<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>JVM 上篇-内存与垃圾回收 | 无人深空</title><meta name="keywords" content="JVM"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="JVM 上篇-内存与垃圾回收1.JVM 与 Java 体系结构1.Java 及 JVM 简介 Java 是跨平台的语言；JVM 是跨语言的平台  Java 是跨平台的语言      JVM 是跨语言的平台    字节码  我们平时说的 java 字节码，指的是用 java 语言编译成的字节码。准确的说任何能在 jvm 平台上执行的字节码格式都是一样的。所以应该统称为：jvm 字节码 不同的编译器，">
<meta property="og:type" content="article">
<meta property="og:title" content="JVM 上篇-内存与垃圾回收">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/09/28/JVM%20%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="JVM 上篇-内存与垃圾回收1.JVM 与 Java 体系结构1.Java 及 JVM 简介 Java 是跨平台的语言；JVM 是跨语言的平台  Java 是跨平台的语言      JVM 是跨语言的平台    字节码  我们平时说的 java 字节码，指的是用 java 语言编译成的字节码。准确的说任何能在 jvm 平台上执行的字节码格式都是一样的。所以应该统称为：jvm 字节码 不同的编译器，">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg">
<meta property="article:published_time" content="2022-09-28T10:29:46.000Z">
<meta property="article:modified_time" content="2022-10-06T12:16:29.203Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="JVM">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/09/28/JVM%20%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'JVM 上篇-内存与垃圾回收',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-10-06 20:16:29'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">JVM 上篇-内存与垃圾回收</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-09-28T10:29:46.000Z" title="发表于 2022-09-28 18:29:46">2022-09-28</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-10-06T12:16:29.203Z" title="更新于 2022-10-06 20:16:29">2022-10-06</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java/">Java</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">38.7k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>126分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="JVM 上篇-内存与垃圾回收"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="JVM-上篇-内存与垃圾回收"><a href="#JVM-上篇-内存与垃圾回收" class="headerlink" title="JVM 上篇-内存与垃圾回收"></a><a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1PJ411n7xZ">JVM 上篇-内存与垃圾回收</a></h2><h3 id="1-JVM-与-Java-体系结构"><a href="#1-JVM-与-Java-体系结构" class="headerlink" title="1.JVM 与 Java 体系结构"></a>1.JVM 与 Java 体系结构</h3><h4 id="1-Java-及-JVM-简介"><a href="#1-Java-及-JVM-简介" class="headerlink" title="1.Java 及 JVM 简介"></a>1.Java 及 JVM 简介</h4><ol>
<li><p>Java 是跨平台的语言；JVM 是跨语言的平台</p>
<blockquote>
<p>Java 是跨平台的语言</p>
</blockquote>
 <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java%E8%B7%A8%E5%B9%B3%E5%8F%B0.png" alt="Java跨平台" style="zoom: 80%;" />

<blockquote>
<p>JVM 是跨语言的平台</p>
</blockquote>
<img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM%E8%B7%A8%E8%AF%AD%E8%A8%80.png" alt="JVM跨语言" style="zoom:80%;" />
</li>
<li><p>字节码</p>
<ul>
<li>我们平时说的 java 字节码，指的是用 java 语言编译成的字节码。准确的说任何能在 jvm 平台上执行的字节码格式都是一样的。所以应该统称为：<strong>jvm 字节码</strong></li>
<li>不同的编译器，可以编译出相同的字节码文件，字节码文件也可以在不同的 JVM 上运行</li>
<li>Java 虚拟机与 Java 语言并没有必然的联系，它只与特定的二进制文件格式—Class 文件格式所关联，Class 文件中包含了 Java 虚拟机指令集（或者称为字节码、Bytecodes）和符号表，还有一些其他辅助信息</li>
</ul>
</li>
<li><p>多语言混合编程</p>
<ul>
<li>Java 平台上的多语言混合编程正成为主流，通过特定领域的语言去解决特定领域的问题是当前软件开发应对日趋复杂的项目需求的一个方向</li>
<li>试想在一个项目之中，并行处理用 Clojure 语言编写，展示层使用 JRuby&#x2F;Rails，中间层则是 Java，每个应用层都将使用不同的编程语言来完成，而且，接口对每一层的开发者都是透明的，<strong>各种语言之间的交互不存在任何困难，就像使用自己语言的原生 API 一样方便，因为它们最终都运行在一个虚拟机之上</strong></li>
<li>对这些运行于 Java 虚拟机之上、Java 之外的语言，来自系统级的、底层的支持正在迅速增强，以 JSR-292 为核心的一系列项目和功能改进（如 Da Vinci Machine 项目、Nashorn 引擎、InvokeDynamic 指令、java.lang.invoke 包等），<strong>推动 Java 虚拟机从“Java 语言的虚拟机”向 “多语言虚拟机”的方向发展</strong></li>
</ul>
</li>
</ol>
<h4 id="2-Java-发展的重大事件"><a href="#2-Java-发展的重大事件" class="headerlink" title="2.Java 发展的重大事件"></a>2.Java 发展的重大事件</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java%E9%87%8D%E5%A4%A7%E4%BA%8B%E4%BB%B6.png" alt="Java重大事件"></p>
<h4 id="3-虚拟机与-Java-虚拟机"><a href="#3-虚拟机与-Java-虚拟机" class="headerlink" title="3.虚拟机与 Java 虚拟机"></a>3.虚拟机与 Java 虚拟机</h4><ol>
<li><p>虚拟机</p>
<blockquote>
<p>所谓虚拟机（Virtual Machine），就是一台虚拟的计算机。它是一款软件，用来执行一系列虚拟计算机指令。大体上，虚拟机可以分为<strong>系统虚拟机</strong>和<strong>程序虚拟机</strong></p>
</blockquote>
<ul>
<li>大名鼎鼎的 VisualBox，VMware 就属于系统虚拟机，它们完全是对物理计算机的仿真，提供了一个可运行完整操作系统的软件平台</li>
<li>程序虚拟机的典型代表就是 Java 虚拟机，它专门为执行单个计算机程序而设计，在 Java 虚拟机中执行的指令我们称为 Java 字节码指令</li>
</ul>
</li>
<li><p>Java 虚拟机</p>
<ul>
<li><p>Java 虚拟机是一台执行 Java 字节码的虚拟计算机，它拥有独立的运行机制，<strong>其运行的 Java 字节码也未必由 Java 语言编译而成</strong></p>
</li>
<li><p>JVM 平台的各种语言可以共享 Java 虚拟机带来的跨平台性、优秀的垃圾回器，以及可靠的即时编译器</p>
</li>
<li><p>Java 技术的核心就是 Java 虚拟机（JVM，Java Virtual Machine），因为<strong>所有的 Java 程序都运行在 Java 虚拟机内部</strong></p>
</li>
<li><p>Java 虚拟机作用</p>
<p><strong>Java 虚拟机就是二进制字节码的运行环境</strong>，负责装载字节码到其内部，解释 &#x2F; 编译为对应平台上的机器指令执行。每一条 Java 指令，Java 虚拟机规范中都有详细定义，如怎么取操作数，怎么处理操作数，处理结果放在哪里</p>
</li>
<li><p>Java 虚拟机特点</p>
<ul>
<li>一次编译，到处运行</li>
<li>自动内存管理</li>
<li>自动垃圾回收功能</li>
</ul>
</li>
<li><p>三大主流 Java 虚拟机</p>
<ul>
<li>HotSpot（默认）</li>
<li>JRockit</li>
<li>J9</li>
</ul>
</li>
</ul>
</li>
<li><p>JVM 在系统层面位置</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM%E7%B3%BB%E7%BB%9F%E4%BD%8D%E7%BD%AE.png" alt="JVM系统位置"></p>
</li>
<li><p>JDK、JRE、JVM 包含关系</p>
<blockquote>
<p>JDK：Java Development Kit —— Java 开发工具包</p>
<p>JRE：Java Runtime Environment —— Java 运行时环境</p>
<p>JVM：Java Virtual Machine —— Java 虚拟机</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK%E3%80%81JRE%E3%80%81JVM.png" alt="JDK、JRE、JVM"></p>
</li>
</ol>
<h4 id="4-JVM-的整体结构"><a href="#4-JVM-的整体结构" class="headerlink" title="4.JVM 的整体结构"></a>4.JVM 的整体结构</h4><blockquote>
<p>以 HotSpot 虚拟机为例</p>
<ul>
<li>采用解释器与即时编译器并存的架构</li>
<li>在今天，Java 程序的运行性能早已脱胎换骨，已经达到了可以和 C&#x2F;C++程序一较高下的地步</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84.png" alt="JVM整体结构"></p>
</blockquote>
<h4 id="5-Java-代码执行流程"><a href="#5-Java-代码执行流程" class="headerlink" title="5.Java 代码执行流程"></a>5.Java 代码执行流程</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/Java%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B.png" alt="Java代码执行流程"></p>
<h4 id="6-JVM-的架构模型"><a href="#6-JVM-的架构模型" class="headerlink" title="6.JVM 的架构模型"></a>6.JVM 的架构模型</h4><blockquote>
<p>Java 编译器输入的指令流基本上是一种<strong>基于栈的指令集架构</strong>，另外一种指令集架构则是<strong>基于寄存器的指令集架构</strong></p>
<p>HotSpot 是基于栈的指令集结构，只有 <strong>PC 寄存器</strong>这一个寄存器</p>
</blockquote>
<ol>
<li><p>两种指令集架构对比</p>
<ul>
<li><p><strong>基于栈式架构的特点</strong></p>
<ul>
<li><p>设计和实现更简单，适用于资源受限的系统</p>
</li>
<li><p>避开了寄存器的分配难题：使用零地址指令方式分配</p>
</li>
<li><p>指令流中的指令大部分是零地址指令，其执行过程依赖于操作栈。指令集更小，编译器容易实现</p>
</li>
<li><p>不需要硬件支持，可移植性更好，更好实现跨平台</p>
</li>
</ul>
</li>
<li><p><strong>基于寄存器架构的特点</strong></p>
<ul>
<li><p>典型的应用是 x86 的二进制指令集：比如传统的 PC 以及 Android 的 Davlik 虚拟机</p>
</li>
<li><p>指令集架构则完全依赖硬件，可移植性差</p>
</li>
<li><p>性能优秀和执行更高效</p>
</li>
<li><p>花费更少的指令去完成一项操作</p>
</li>
</ul>
</li>
<li><p>字节码对比，以执行 <code>2 + 3</code> 操作为例</p>
<blockquote>
<p>基于栈的计算流程</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">iconst_2 <span class="comment">//常量2入栈</span></span><br><span class="line">istore_1</span><br><span class="line">iconst_3 <span class="comment">// 常量3入栈</span></span><br><span class="line">istore_2</span><br><span class="line">iload_1</span><br><span class="line">iload_2</span><br><span class="line">iadd <span class="comment">//常量2/3出栈，执行相加</span></span><br><span class="line">istore_0 <span class="comment">// 结果5入栈</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>基于寄存器的计算流程</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">mov eax,<span class="number">2</span> <span class="comment">//将eax寄存器的值设为1</span></span><br><span class="line">add eax,<span class="number">3</span> <span class="comment">//使eax寄存器的值加3</span></span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>总结</p>
<ul>
<li><p>在大部分情况下，<strong>基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主，而基于栈式架构的指令集却是以零地址指令为主</strong></p>
</li>
<li><p><strong>由于跨平台性的设计，Java 的指令都是根据栈来设计的</strong>。不同平台 CPU 架构不同，所以不能设计为基于寄存器的</p>
<ul>
<li>优点：跨平台，指令集小，编译器容易实现</li>
<li>缺点：性能下降，实现同样的功能需要更多的指令</li>
</ul>
</li>
<li><p>时至今日，尽管嵌入式平台已经不是 Java 程序的主流运行平台了（准确来说应该是 HotSpotVM 的宿主环境已经不局限于嵌入式平台（资源受限）了），那么为什么不将架构更换为基于寄存器的架构呢？</p>
<blockquote>
<p>A：基于栈的设计和实现更加简单、在非资源受限的情况也是可以投入使用的</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="7-JVM-的生命周期"><a href="#7-JVM-的生命周期" class="headerlink" title="7.JVM 的生命周期"></a>7.JVM 的生命周期</h4><ol>
<li>虚拟机的启动<ul>
<li>Java 虚拟机的启动是通过引导类加载器（bootstrap class loader）创建一个初始类（initial class）来完成的，这个类是由虚拟机的具体实现指定的</li>
</ul>
</li>
<li>虚拟机的执行<ul>
<li>一个运行中的 Java 虚拟机有着一个清晰的任务：执行 Java 程序</li>
<li>程序开始执行时他才运行，程序结束时他就停止</li>
<li><strong>执行一个所谓的 Java 程序的时候，真真正正在执行的是一个叫做 Java 虚拟机的进程</strong></li>
</ul>
</li>
<li>虚拟机的退出<ul>
<li>程序正常执行结束</li>
<li>程序在执行过程中遇到了异常或错误而异常终止</li>
<li>由于操作系统用现错误而导致 Java 虚拟机进程终止</li>
<li>某线程调用 Runtime 类或 system 类的 exit 方法，或 Runtime 类的 halt 方法，并且 Java 安全管理器也允许这次 exit 或 halt 操作。</li>
<li>除此之外，JNI（Java Native Interface）规范描述了用 JNI Invocation API 来加载或卸载 Java 虚拟机时，Java 虚拟机的退出情况</li>
</ul>
</li>
</ol>
<h4 id="8-JVM-的发展历程"><a href="#8-JVM-的发展历程" class="headerlink" title="8.JVM 的发展历程"></a>8.JVM 的发展历程</h4><ol>
<li>主流三大虚拟机：<code>HotSpot VM</code>、<code>JRockit</code>、<code>J9</code></li>
<li>其余虚拟机：<code>Sun Classic VM</code>、<code>Exact VM</code>、<code>Azul VM</code>、<code>Liquid VM</code>、<code>Apache Harmony</code>、<code>Taobao JVM</code>、<code>Micorsoft JVM</code>、<code>Dalvik VM</code>、<code>Graal VM</code>、<code>KVM 和 CDC / CLDC Hotspot</code></li>
<li>具体 JVM 的内存结构，其实取决于其实现，不同厂商的 JVM，或者同一厂商发布的不同版本，都有可能存在一定差异。<strong>主要以 Oracle HotSpot VM 为默认虚拟机</strong></li>
</ol>
<h3 id="2-类加载子系统"><a href="#2-类加载子系统" class="headerlink" title="2.类加载子系统"></a>2.类加载子系统</h3><h4 id="1-内存结构概述"><a href="#1-内存结构概述" class="headerlink" title="1.内存结构概述"></a>1.内存结构概述</h4><blockquote>
<p>内存结构简图</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84%E6%A6%82%E8%BF%B0.png" alt="内存结构概述"></p>
</blockquote>
<blockquote>
<p>内存结构详细图</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84%E8%AF%A6%E8%A7%A3.png" alt="内存结构详解"></p>
</blockquote>
<ol>
<li><p>主要分为以下几个部分</p>
<p>Class 文件、类加载子系统、运行时数据区（方法区、堆、程序计数器、虚拟机栈、本地方法栈）、执行引擎、本地方法接口、本地方法库</p>
</li>
<li><p>如果自己想手写一个 Java 虚拟机的话，主要考虑哪些结构</p>
<ul>
<li>类加载器</li>
<li>执行引擎</li>
</ul>
</li>
</ol>
<h4 id="2-类加载器与类加载过程"><a href="#2-类加载器与类加载过程" class="headerlink" title="2.类加载器与类加载过程"></a>2.类加载器与类加载过程</h4><ol>
<li><p>类加载子系统的作用</p>
<ul>
<li>类加载器子系统负责从文件系统或者网络中加载 Class 文件，class 文件在文件开头有特定的文件标识 —— 魔数 <code>CAFEBABE</code></li>
<li>ClassLoader 只负责 class 文件的加载，至于它是否可以运行，则由 Execution Engine 决定</li>
<li>加载的类信息存放于一块称为方法区的内存空间。除了类的信息外，方法区中还会存放运行时常量池信息，可能还包括字符串字面量和数字常量（这部分常量信息是 Class 文件中常量池部分的内存映射）</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F.png" alt="类加载子系统"></p>
</li>
<li><p>类加载器 ClassLoader 角色</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/classLoader.png" alt="classLoader"></p>
<ul>
<li>class file 存在于本地硬盘上，可以理解为设计师画在纸上的模板，而最终这个模板在执行的时候是要加载到 JVM 当中来根据这个文件实例化出 n 个一模一样的实例</li>
<li>class file 加载到 JVM 中，被称为 DNA 元数据模板，放在方法区</li>
<li><strong>在 .class 文件 -&gt; JVM -&gt; 最终成为元数据模板，此过程就要一个运输工具（类装载器 Class Loader），扮演一个快递员的角色</strong></li>
</ul>
</li>
<li><p>类加载过程</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *示例代码</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloLoader</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello World!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>上述代码加载流程图展示</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E5%9B%BE.png" alt="类加载流程图"></p>
</blockquote>
<blockquote>
<p>广义的加载分为如下三个阶段：加载（狭义）、链接、初始化</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8A%A0%E8%BD%BD%E9%93%BE%E6%8E%A5%E5%88%9D%E5%A7%8B%E5%8C%96.png" alt="加载链接初始化"></p>
</blockquote>
<ul>
<li><p>加载阶段</p>
<ul>
<li>通过一个类的全限定名获取定义此类的二进制字节流</li>
<li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构</li>
<li>在内存中生成一个<strong>代表这个类的 java.lang.Class 对象</strong>，作为方法区这个类的各种数据的访问入口</li>
</ul>
<blockquote>
<p>class 文件来源</p>
<ul>
<li>从<strong>本地系统</strong>中直接加载</li>
<li>通过<strong>网络</strong>获取，典型场景：Web Applet</li>
<li>从 <strong>zip 压缩包</strong>中读取，成为日后 jar、war 格式的基础</li>
<li>运行时计算生成，使用最多的是：<strong>动态代理技术</strong></li>
<li>由其他文件生成，典型场景：<strong>JSP 应用</strong></li>
<li>从<strong>专有数据库</strong>中提取.class 文件，比较少见</li>
<li>从<strong>加密文件</strong>中获取，典型的防 Class 文件被反编译的保护措施</li>
</ul>
</blockquote>
</li>
<li><p>链接阶段</p>
<ul>
<li>验证（Verify）<ul>
<li>目的在子确保 Class 文件的字节流中包含信息符合当前虚拟机要求，保证被加载类的正确性，不会危害虚拟机自身安全</li>
<li>主要包括四种验证，<strong>文件格式验证，元数据验证，字节码验证，符号引用验证</strong></li>
</ul>
</li>
<li>准备（Prepare）<ul>
<li>为类变量分配内存并且设置该类变量的默认初始值，即零值。</li>
<li>这里不包含用 final 修饰的 static，因为 final 在编译的时候就会分配了，<strong>准备阶段会显式初始化</strong></li>
<li>这里不会为实例变量分配初始化，类变量会分配在方法区中，而实例变量是会随着对象一起分配到 Java 堆中</li>
</ul>
</li>
<li>解析（Resolve）<ul>
<li>将常量池内的<strong>符号引用转换为直接引用</strong>的过程</li>
<li>事实上，<strong>解析操作往往会伴随着 JVM 在执行完初始化之后再执行</strong></li>
<li>符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java 虚拟机规范》的 Class 文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄</li>
<li>解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的 CONSTANT_Class_info，CONSTANT_Fieldref_info、CONSTANT_Methodref_info 等</li>
</ul>
</li>
</ul>
</li>
<li><p>初始化阶段</p>
<ul>
<li>初始化阶段就是执行<strong>类构造器方法 <code>&lt;clinit&gt;()</code></strong> 的过程（不同于**类的构造器 <code>&lt;init&gt;()</code>**）</li>
<li>此方法不需定义，<strong>是 javac 编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来</strong></li>
<li><strong>构造器方法中指令按语句在源文件中出现的顺序执行</strong></li>
<li><code>&lt;clinit&gt;()</code> 不同于类的构造器。（关联：构造器是虚拟机视角下的 <code>&lt;init&gt;()</code>）</li>
<li><strong>若该类具有父类，JVM 会保证子类的 <code>&lt;clinit&gt;()</code> 执行前，父类的 <code>&lt;clinit&gt;()</code> 已经执行完毕</strong></li>
<li>虚拟机必须保证一个类的 <code>&lt;clinit&gt;()</code> 方法<strong>在多线程下被同步加锁</strong></li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="3-类加载器分类"><a href="#3-类加载器分类" class="headerlink" title="3.类加载器分类"></a>3.类加载器分类</h4><blockquote>
<p>类加载器分类层次结构（注意下图表示<strong>包含关系</strong>，<strong>而非分层或子父类的继承关系</strong>）</p>
<p>类加载器之间是<strong>组合关系</strong>，“子类”加载器有个 <code>parentClassLoader</code> 属性，指向“父类”加载器对象</p>
<p>分类1：</p>
<ul>
<li>虚拟机自带加载器：引导类加载器、扩展类加载器、系统类加载器</li>
<li>用户自定义加载器</li>
</ul>
<p>分类2：</p>
<ul>
<li>引导类加载器（C &#x2F; C++ 编写）</li>
<li>自定义类加载器（Java 语言编写，所有派生于引导类加载器的加载器，包括扩展类加载器、系统类加载器）</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%88%86%E7%B1%BB.png" alt="类加载器分类"></p>
</blockquote>
<blockquote>
<p>虚拟机自带加载器</p>
</blockquote>
<ol>
<li>启动类加载器（引导类加载器 <strong>BootStrap ClassLoader</strong>）<ul>
<li><strong>这个类加载使用 C&#x2F;C++语言实现的，嵌套在 JVM 内部</strong></li>
<li>它<strong>用来加载 Java 的核心库</strong>（JAVA_HOME&#x2F;jre&#x2F;lib&#x2F;rt.jar、resources.jar 或 sun.boot.class.path 路径下的内容），用于提供 JVM 自身需要的类</li>
<li>并不继承自 ava.lang.ClassLoader，<strong>没有父加载器</strong></li>
<li>加载扩展类和应用程序类加载器，并指定为他们的父类加载器</li>
<li>出于安全考虑，Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类</li>
</ul>
</li>
<li>扩展类加载器（<strong>Extension ClassLoader</strong>）<ul>
<li>Java 语言编写，由 sun.misc.Launcher$ExtClassLoader 实现</li>
<li>派生于 ClassLoader 类</li>
<li>父类加载器为启动类加载器</li>
<li>从 java.ext.dirs 系统属性所指定的目录中加载类库，或从 JDK 的安装目录的 jre&#x2F;1ib&#x2F;ext 子目录（扩展目录）下加载类库。如果用户创建的 JAR 放在此目录下，也会自动由扩展类加载器加载</li>
</ul>
</li>
<li>应用程序类加载器（<strong>App ClassLoader</strong> 系统类加载器 <strong>System ClassLoader</strong>）<ul>
<li>java 语言编写，由 sun.misc.LaunchersAppClassLoader 实现</li>
<li>派生于 ClassLoader 类</li>
<li>父类加载器为扩展类加载器</li>
<li>它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库</li>
<li><strong>该类加载是程序中默认的类加载器</strong>，一般来说，Java 应用的类都是由它来完成加载</li>
<li>通过 <code>ClassLoader.getSystemclassLoader()</code> 方法可以获取到该类加载器</li>
</ul>
</li>
</ol>
<blockquote>
<p>用户自定义类加载器</p>
<p>在 Java 的日常应用程序开发中，类的加载几乎是由上述 3 种类加载器相互配合执行的，在必要时，我们还可以自定义类加载器，来定制类的加载方式</p>
</blockquote>
<ol start="4">
<li>用户自定义类加载器<ul>
<li>为什么要自定义类加载器？<ul>
<li>隔离加载类</li>
<li>修改类加载的方式</li>
<li>扩展加载源</li>
<li>防止源码泄漏</li>
</ul>
</li>
<li>自定义类加载器实现步骤<ul>
<li>开发人员可以通过继承抽象类 <code>Java.lang.ClassLoader</code> 类的方式，实现自己的类加载器，以满足一些特殊的需求</li>
<li>在 JDK1.2 之前，在自定义类加载器时，总会去继承 <code>ClassLoader 类</code>并重写 <code>loadClass()</code> 方法，从而实现自定义的类加载类，但是在 JDK1.2 之后已不再建议用户去覆盖 <code>loadclass()</code> 方法，而是建议把自定义的类加载逻辑写在 <code>findClass()</code> 方法中</li>
<li>在编写自定义类加载器时，如果没有太过于复杂的需求，可以直接继承 <code>URLClassLoader 类</code>，这样就可以避免自己去编写 <code>findClass()</code> 方法及其获取字节码流的方式，使自定义类加载器编写更加简洁</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="4-ClassLoader-的使用说明"><a href="#4-ClassLoader-的使用说明" class="headerlink" title="4.ClassLoader 的使用说明"></a>4.ClassLoader 的使用说明</h4><ol>
<li><p>ClassLoader 类是一个抽象类，其后所有的类加载器都继承自 ClassLoader（不包括启动类加载器）</p>
<blockquote>
<p>sun.misc.Launcher 它是一个 java 虚拟机的入口应用</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/classLoader%E7%B1%BB%E5%9B%BE.png" alt="classLoader类图"></p>
</li>
<li><p>ClassLoader 方法</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/classLoader%E6%96%B9%E6%B3%95.png" alt="classLoader方法"></p>
</li>
<li><p>获取 ClassLoader 的途径</p>
<ul>
<li>获取当前 ClassLoader：<code>clazz.getClassLoader()</code></li>
<li>获取当前线程上下文的 ClassLoader：<code>Thread.currentThread().getContextClassLoader()</code></li>
<li>获取系统的 ClassLoader：<code>ClassLoader.getSystemClassLoader()</code></li>
<li>获取调用者的 ClassLoader：<code>DriverManager.getCallerClassLoader()</code></li>
</ul>
</li>
</ol>
<h4 id="5-双亲委派机制"><a href="#5-双亲委派机制" class="headerlink" title="5.双亲委派机制"></a>5.双亲委派机制</h4><blockquote>
<p>Java 虚拟机对 class 文件采用的是<strong>按需加载</strong>的方式，即当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象</p>
<p>加载某个类的 class 文件时，Java 虚拟机采用的是<strong>双亲委派模式</strong>，即把请求交由父类处理，它是一种任务委派模式</p>
</blockquote>
<ol>
<li><p>工作原理</p>
<ul>
<li>如果一个类加载器收到了类加载请求，它并不会自己先去加载，而是把这个请求委托给父类的加载器去执行</li>
<li>如果父类加载器还存在其父类加载器，则进一步向上委托，依次递归，请求最终将到达顶层的启动类加载器</li>
<li>如果父类加载器可以完成类加载任务，就成功返回，倘若父类加载器无法完成此加载任务，子加载器才会尝试自己去加载</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%90%91%E4%B8%8A%E5%A7%94%E6%89%98.png" alt="向上委托"></p>
</li>
<li><p>双亲委派机制举例</p>
<blockquote>
<p>当我们加载 jdbc.jar 用于实现数据库连接的时候，首先我们需要知道的是 jdbc.jar 是基于 SPI 接口进行实现的，所以在加载的时候，会进行双亲委派，最终从根加载器中加载 SPI 核心类，然后在加载 SPI 接口类，接着在进行反向委派，通过线程上下文类加载器进行实现类 jdbc.jar 的加载</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%90%91%E4%B8%8B%E5%A7%94%E6%B4%BE.png" alt="向下委派"></p>
</blockquote>
</li>
<li><p>双亲委派机制优势</p>
<ul>
<li>避免类的重复加载</li>
<li><strong>保护程序安全，防止核心 API 被随意篡改</strong>（即沙箱安全机制）<ul>
<li>自定义类：java.lang.String（不会被使用，使用的永远是 Java 自带的 String 类）</li>
<li>自定义类：java.lang.ShkStart（报错：阻止创建 java.lang 开头的类）</li>
</ul>
</li>
</ul>
</li>
<li><p>沙箱安全机制</p>
<ul>
<li>自定义 String 类，但是在加载自定义 String 类的时候会率先使用引导类加载器加载，而引导类加载器在加载的过程中会先加载 jdk 自带的文件（rt.jar 包中 java\lang\String.class），加载的是 rt.jar 包中的 string 类。这样可以保证对 <strong>java 核心源代码的保护</strong>，这就是<strong>沙箱安全机制</strong></li>
</ul>
</li>
</ol>
<h4 id="6-其他"><a href="#6-其他" class="headerlink" title="6.其他"></a>6.其他</h4><ol>
<li><p>如何判断两个 class 对象是否相同</p>
<blockquote>
<p>在 JVM 中表示两个 class 对象是否为同一个类存在两个必要条件：</p>
<ul>
<li>类的完整类名必须一致，包括包名。</li>
<li>加载这个类的 ClassLoader（指 ClassLoader 实例对象）必须相同</li>
</ul>
<p>换句话说，在 JVM 中，即使这两个类对象（class 对象）来源同一个 Class 文件，被同一个虚拟机所加载，但只要加载它们的 ClassLoader 实例对象不同，那么这两个类对象也是不相等的</p>
</blockquote>
</li>
<li><p>类加载器的引用</p>
<blockquote>
<p>JVM 必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由<strong>用户类加载器加载</strong>的，那么 <strong>JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中</strong>。当解析一个类型到另一个类型的引用的时候，JVM 需要保证这两个类型的类加载器是相同的</p>
</blockquote>
</li>
<li><p>类的主动使用和被动使用</p>
<blockquote>
<p>Java 程序对类的使用方式分为：主动使用和被动使用</p>
</blockquote>
<ul>
<li><p>主动使用</p>
<ul>
<li><p>创建类的实例</p>
</li>
<li><p>访问某个类或接口的静态变量，或者对该静态变量赋值</p>
</li>
<li><p>调用类的静态方法</p>
</li>
<li><p>反射（比如：Class.forName（”com.atguigu.Test”））</p>
</li>
<li><p><strong>初始化一个类的子类</strong></p>
</li>
<li><p>Java 虚拟机启动时被标明为启动类的类</p>
</li>
<li><p>JDK 7 开始提供的动态语言支持：</p>
<p>java.lang.invoke.MethodHandle 实例的解析结果</p>
<p>REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化，则初始化</p>
</li>
</ul>
</li>
<li><p>被动使用</p>
<ul>
<li>除了以上七种情况，其他使用 Java 类的方式都被看作是对<strong>类的被动使用</strong>，都<strong>不会导致类的初始化</strong></li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="3-运行时数据区概述及线程"><a href="#3-运行时数据区概述及线程" class="headerlink" title="3.运行时数据区概述及线程"></a>3.运行时数据区概述及线程</h3><h4 id="1-运行时数据区"><a href="#1-运行时数据区" class="headerlink" title="1.运行时数据区"></a>1.运行时数据区</h4><ol>
<li><p>运行时数据区在内存结构中的位置</p>
<blockquote>
<p>类加载完成后进入到运行时数据区</p>
<p>通过前面的：类的加载-&gt; 验证 -&gt; 准备 -&gt; 解析 -&gt; 初始化 这几个阶段完成后，就会用到执行引擎对所加载类进行使用，同时执行引擎将会使用到运行时数据区</p>
<p><strong>每个 JVM 只有一个 Runtime 实例。即为运行时环境</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E4%BD%8D%E7%BD%AE.png" alt="运行时数据区位置"></p>
</li>
<li><p>运行时数据区结构</p>
<blockquote>
<p>内存是非常重要的系统资源，是硬盘和 CPU 的中间仓库及桥梁，承载着操作系统和应用程序的实时运行 JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略，保证了 JVM 的高效稳定运行。</p>
<p>不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。结合 JVM 虚拟机规范，来探讨一下经典的 JVM 内存布局</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA.png" alt="运行时数据区"></p>
<p>通过磁盘或者网络 IO 得到的数据，都需要先加载到内存中，然后 CPU 从内存中获取数据进行读取，也就是说<strong>内存充当了 CPU 和磁盘之间的桥梁</strong></p>
</blockquote>
</li>
<li><p>运行时数据区结构详解</p>
<blockquote>
<p>Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区，其中有一些会随着虚拟机启动而创建，随着虚拟机退出而销毁。另外一些则是与线程一一对应的，这些与线程对应的数据区域会随着线程开始和结束而创建和销毁</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E8%AF%A6%E8%A7%A3.png" alt="运行时数据区详解"></p>
</blockquote>
<ul>
<li>线程独享：程序计数器、本地方法栈、虚拟机栈（栈帧）</li>
<li>线程间共享：堆、堆外内存（方法区（永久代或元空间）、代码缓存）</li>
</ul>
</li>
</ol>
<h4 id="2-线程"><a href="#2-线程" class="headerlink" title="2.线程"></a>2.线程</h4><ol>
<li><p>线程</p>
<ul>
<li>线程是一个程序里的运行单元。JVM 允许一个应用有多个线程并行的执行。 在 Hotspot JVM 里，<strong>每个线程都与操作系统的本地线程直接映射</strong>（1 对 1 映射）</li>
<li><strong>当一个 Java 线程准备好执行以后，此时一个操作系统的本地线程也同时创建。Java 线程执行终止后，本地线程也会回收</strong></li>
<li>操作系统负责所有线程的安排调度到任何一个可用的 CPU 上。一旦本地线程初始化成功，它就会调用 Java 线程中的 <code>run()</code> 方法</li>
</ul>
</li>
<li><p>JVM 系统线程</p>
<blockquote>
<p>如果使用 console 或者是任何一个调试工具，都能看到在后台有许多线程在运行。这些后台线程不包括调用<code>public static void main(String[] args)</code>的 main 线程以及所有这个 main 线程自己创建的线程</p>
</blockquote>
<p>后台系统线程在 Hotspot JVM 里主要是以下几个：</p>
<ul>
<li><strong>虚拟机线程</strong>：这种线程的操作是需要 JVM 达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要 JVM 达到安全点，这样堆才不会变化。这种线程的执行类型包括”stop-the-world”的垃圾收集，线程栈收集，线程挂起以及偏向锁撤销</li>
<li><strong>周期任务线程</strong>：这种线程是时间周期事件的体现（比如中断），他们一般用于周期性操作的调度执行</li>
<li><strong>GC 线程</strong>：这种线程对在 JVM 里不同种类的垃圾收集行为提供了支持</li>
<li><strong>编译线程</strong>：这种线程在运行时会将字节码编译成到本地代码</li>
<li><strong>信号调度线程</strong>：这种线程接收信号并发送给 JVM，在它内部通过调用适当的方法进行处理</li>
</ul>
</li>
</ol>
<h3 id="4-程序计数器（PC-寄存器）"><a href="#4-程序计数器（PC-寄存器）" class="headerlink" title="4.程序计数器（PC 寄存器）"></a>4.程序计数器（PC 寄存器）</h3><h4 id="1-程序计数器"><a href="#1-程序计数器" class="headerlink" title="1.程序计数器"></a>1.程序计数器</h4><ol>
<li><p>程序计数器介绍</p>
<blockquote>
<p>JVM 中的程序计数寄存器（Program Counter Register）中，Register 的命名源于 CPU 的寄存器，寄存器存储指令相关的现场信息。CPU 只有把数据装载到寄存器才能够运行。这里并非是广义上所指的物理寄存器，或许将其翻译为 PC 计数器（或指令计数器）会更加贴切（也称为程序钩子），并且也不容易引起一些不必要的误会。<strong>JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/PC%E5%AF%84%E5%AD%98%E5%99%A8%E4%BD%8D%E7%BD%AE.png" alt="PC寄存器位置"></p>
</blockquote>
<ul>
<li>PC 寄存器的作用<ul>
<li>PC 寄存器用来存储指向下一条指令的地址，也即将要执行的指令代码。由执行引擎读取下一条指令</li>
<li>它是一块很小的内存空间，几乎可以忽略不记。也是<strong>运行速度最快的存储区域</strong></li>
<li>在 JVM 规范中，每个线程都有它自己的程序计数器，<strong>是线程私有的，生命周期与线程的生命周期保持一致</strong></li>
<li><strong>任何时间一个线程都只有一个方法在执行，也就是所谓的当前方法</strong>。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址；或者，如果是在执行 native 方法，则是未指定值（undefined）</li>
<li>它是程序控制流的指示器，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成</li>
<li><strong>字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令</strong></li>
<li><strong>它是唯一一个在 Java 虚拟机规范中没有规定任何 OutofMemoryError 情况的区域</strong></li>
</ul>
</li>
</ul>
</li>
<li><p>举例说明</p>
<blockquote>
<p>简单示例代码如下</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">minus</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">d</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line">    <span class="keyword">return</span> c - d;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>对应字节码文件</p>
</blockquote>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">0: iconst_3</span><br><span class="line">1: istore_1</span><br><span class="line">2: iconst_4</span><br><span class="line">3: istore_2</span><br><span class="line">4: iload_1</span><br><span class="line">5: iload_2</span><br><span class="line">6: isub</span><br><span class="line">7: ireturn</span><br></pre></td></tr></table></figure>
</li>
<li><p>使用说明</p>
<ul>
<li><p>为什么使用 PC 寄存器记录当前线程的执行地址</p>
<blockquote>
<p>因为 CPU 需要不停的切换各个线程，这时候切换回来以后，就得知道接着从哪开始继续执行</p>
<p>JVM 的字节码解释器需要通过改变 PC 寄存器的值来明确下一条应该执行什么样的字节码指令</p>
</blockquote>
</li>
<li><p>PC 寄存器为什么被设定为私有</p>
<blockquote>
<p>由于 CPU 时间片轮转限制，众多线程在并发执行过程中，任何一个确定的时刻，一个处理器或者多核处理器中的一个内核，只会执行某个线程中的一条指令，CPU 会不停地做任务切换，这样必然导致经常中断或恢复，</p>
<p><strong>为了能够准确地记录各个线程正在执行的当前字节码指令地址，最好的办法是为每一个线程都分配一个 PC 寄存器</strong>，这样一来各个线程之间便可以进行独立计算，从而不会出现相互干扰的情况</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="2-CPU-时间片"><a href="#2-CPU-时间片" class="headerlink" title="2.CPU 时间片"></a>2.CPU 时间片</h4><ol>
<li>CPU 时间片<ul>
<li>CPU 时间片即 CPU 分配给各个程序的时间，每个线程被分配一个时间段，称作它的时间片<ul>
<li>在宏观上：可以同时打开多个应用程序，每个程序并行不悖，同时运行。</li>
<li>在微观上：由于只有一个 CPU，一次只能处理程序要求的一部分，如何处理公平，一种方法就是引入时间片，每个程序轮流执行</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="5-虚拟机栈"><a href="#5-虚拟机栈" class="headerlink" title="5.虚拟机栈"></a>5.虚拟机栈</h3><h4 id="1-虚拟机栈概述"><a href="#1-虚拟机栈概述" class="headerlink" title="1.虚拟机栈概述"></a>1.虚拟机栈概述</h4><ol>
<li><p>虚拟机栈出现的背景</p>
<blockquote>
<p>由于跨平台性的设计，Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同，所以不能设计为基于寄存器的</p>
</blockquote>
<ul>
<li>优点是跨平台，指令集小，编译器容易实现</li>
<li>缺点是性能下降，实现同样的功能需要更多的指令</li>
</ul>
</li>
<li><p>内存中的堆和栈</p>
<blockquote>
<p>栈是运行时的单位，而堆是存储的单位</p>
</blockquote>
<ul>
<li>栈解决程序的运行问题，即程序如何执行，或者说如何处理数据</li>
<li>堆解决的是数据存储的问题，即数据怎么放，放哪里</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86%E5%92%8C%E6%A0%88.png" alt="堆和栈"></p>
</li>
<li><p>虚拟机栈基本内容</p>
<ul>
<li><p>Java 虚拟机是什么</p>
<blockquote>
<p>Java 虚拟机栈（Java Virtual Machine Stack），早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈，其内部保存一个个的栈帧（Stack Frame），对应着一次次的 Java 方法调用，是线程私有的</p>
</blockquote>
</li>
<li><p>生命周期</p>
<blockquote>
<p>虚拟机栈的生命周期和线程一致</p>
</blockquote>
</li>
<li><p>Java 虚拟机的作用</p>
<blockquote>
<p>主管 Java 程序的运行，它保存方法的局部变量、部分结果，并参与方法的调用和返回</p>
</blockquote>
</li>
<li><p>栈的特点</p>
<blockquote>
<p>栈是一种快速有效的分配存储方式，访问速度仅次于 PC 寄存器</p>
<p>JVM 直接对 Java 栈的操作只有两个：</p>
<ul>
<li>每个方法执行，伴随着进栈（入栈、压栈）</li>
<li>执行结束后的出栈工作</li>
</ul>
<p><strong>对于栈来说不存在垃圾回收问题（栈存在溢出的情况）</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>栈中可能出现的异常</p>
<blockquote>
<p>Java 虚拟机<strong>规范允许 Java 栈的大小是动态的或者是固定不变的</strong></p>
</blockquote>
<ul>
<li><p>如果采用固定大小的 Java 虚拟机栈，那每一个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量，Java 虚拟机将会抛出一个 <code>StackOverflowError</code> 异常</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    test();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">    test();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//程序不断的进行递归调用，而且没有退出条件，就会导致不断地进行压栈</span></span><br><span class="line"><span class="comment">//抛出异常：Exception in thread&quot;main&quot;java.lang.StackoverflowError</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>如果 Java 虚拟机栈可以动态扩展，并且在尝试扩展的时候无法申请到足够的内存，或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈，那 Java 虚拟机将会抛出一个 <code>OutOfMemoryError</code> 异常</p>
</li>
</ul>
</li>
<li><p>设置栈内存大小</p>
<blockquote>
<p>使用参数 <code>-Xss size</code> 选项来设置线程的最大栈空间，栈的大小直接决定了函数调用的最大可达深度</p>
<ul>
<li>-Xss1m</li>
<li>-Xss1024K</li>
<li>-Xss1048576</li>
</ul>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> count= <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">recursion</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println(count);</span><br><span class="line">        count++;</span><br><span class="line">        recursion();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String args[])</span>&#123;</span><br><span class="line">        recursion();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认栈大小</span></span><br><span class="line">count = <span class="number">11459</span></span><br><span class="line"><span class="comment">// 设置栈大小为 -Xss256k</span></span><br><span class="line">count = <span class="number">2535</span></span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="2-栈的存储单位"><a href="#2-栈的存储单位" class="headerlink" title="2.栈的存储单位"></a>2.栈的存储单位</h4><ol>
<li><p>栈中存储内容</p>
<ul>
<li>每个线程都有自己的栈，栈中的数据都是以栈帧（Stack Frame）的格式存在</li>
<li>在这个线程上正在执行的<strong>每个方法都各自对应一个栈帧</strong>（Stack Frame）</li>
<li>栈帧是一个内存区块，是一个数据集，维系着方法执行过程中的各种数据信息</li>
</ul>
</li>
<li><p>栈运行原理</p>
<ul>
<li>JVM 直接对 Java 栈的操作只有两个，就是对<strong>栈帧的压栈和出栈</strong>，<strong>遵循“先进后出”&#x2F;“后进先出”原则</strong></li>
<li><strong>在一条活动线程中，一个时间点上，只会有一个活动的栈帧。</strong>即只有当前正在执行的方法的栈帧（栈顶栈帧）是有效的，这个栈帧被称为<strong>当前栈帧（Current Frame）</strong>，与当前栈帧相对应的方法就是<strong>当前方法（Current Method）</strong>，定义这个方法的类就是<strong>当前类（Current Class）</strong></li>
<li><strong>执行引擎运行的所有字节码指令只针对当前栈帧进行操作</strong></li>
<li>如果在该方法中调用了其他方法，对应的新的栈帧会被创建出来，放在栈的顶端，成为新的当前帧</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%A0%88%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86.png" alt="栈运行原理"></p>
<ul>
<li>不同线程中所包含的栈帧是不允许存在相互引用的，即不可能在一个栈帧之中引用另外一个线程的栈帧</li>
<li>如果当前方法调用了其他方法，方法返回之际，当前栈帧会传回此方法的执行结果给前一个栈帧，接着虚拟机会丢弃当前栈帧，使得前一个栈帧重新成为当前栈帧</li>
<li>Java 方法有两种返回函数的方式（不管使用哪种方式，都会导致栈帧被弹出）<ul>
<li>正常的函数返回，使用 return 指令</li>
<li>抛出异常</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodA</span><span class="params">()</span>&#123;</span><br><span class="line">        system.out.println（<span class="string">&quot;当前栈帧对应的方法-&gt;methodA&quot;</span>);</span><br><span class="line">        methodB();</span><br><span class="line">        system.out.println（<span class="string">&quot;当前栈帧对应的方法-&gt;methodA&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodB</span><span class="params">()</span>&#123;</span><br><span class="line">        System.out.println（<span class="string">&quot;当前栈帧对应的方法-&gt;methodB&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>栈帧的内部结构</p>
<ul>
<li><strong>局部变量表（Local Variables）</strong></li>
<li><strong>操作数栈（operand Stack）（或表达式栈）</strong></li>
<li>动态链接（DynamicLinking）（或指向运行时常量池的方法引用）</li>
<li>方法返回地址（Return Address）（或方法正常退出或者异常退出的定义）</li>
<li>一些附加信息</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%A0%88%E5%B8%A7%E5%86%85%E9%83%A8%E7%BB%93%E6%9E%84.png" alt="栈帧内部结构"></p>
<blockquote>
<p>并行每个线程下的栈都是私有的，因此每个线程都有自己各自的栈，并且每个栈里面都有很多栈帧，栈帧的大小主要由<strong>局部变量表</strong>和<strong>操作数栈</strong>决定的</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%A0%88%E5%B8%A7.png" alt="栈帧"></p>
</blockquote>
</li>
</ol>
<h4 id="3-局部变量表（Local-Variables-本地变量表）"><a href="#3-局部变量表（Local-Variables-本地变量表）" class="headerlink" title="3.局部变量表（Local Variables 本地变量表）"></a>3.局部变量表（Local Variables 本地变量表）</h4><ol>
<li><p>局部变量表的一些概念</p>
<ul>
<li><p>定义为一个<strong>数字数组，主要用于存储方法参数和定义在方法体内的局部变量</strong>，这些数据类型包括各类基本数据类型、对象引用（reference），以及 returnAddress 类型。</p>
</li>
<li><p>由于局部变量表是建立在线程的栈上，是线程的私有数据，因此<strong>不存在数据安全问题</strong></p>
</li>
<li><p><strong>局部变量表所需的容量大小是在编译期确定下来的</strong>，并保存在方法的 Code 属性的 maximum local variables 数据项中。在方法运行期间是不会改变局部变量表的大小的</p>
</li>
<li><p><strong>方法嵌套调用的次数由栈的大小决定</strong>。一般来说，栈越大，方法嵌套调用次数越多。对一个函数而言，它的参数和局部变量越多，使得局部变量表膨胀，它的栈帧就越大，以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间，导致其嵌套调用次数就会减少</p>
</li>
<li><p><strong>局部变量表中的变量只在当前方法调用中有效</strong>。在方法执行时，虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后，随着方法栈帧的销毁，局部变量表也会随之销毁</p>
</li>
</ul>
</li>
<li><p>关于 Slot（变量槽） 的理解</p>
<blockquote>
<p><strong>局部变量表，最基本的存储单元是 Slot（变量槽）</strong></p>
</blockquote>
<ul>
<li>参数值的存放总是在局部变量数组的 index0 开始，到数组长度-1 的索引结束</li>
<li>局部变量表中存放编译期可知的各种基本数据类型（8 种），引用类型（reference），returnAddress 类型的变量</li>
<li>在局部变量表里，32 位以内的类型只占用一个 slot（包括 returnAddress 类型），64 位的类型（long 和 double）占用两个 slot</li>
<li>byte、short、char 在存储前被转换为 int，boolean 也被转换为 int，0 表示 false，非 0 表示 true</li>
<li>JVM 会为局部变量表中的每一个 Slot 都分配一个访问索引，通过这个索引即可成功访问到局部变量表中指定的局部变量值</li>
<li>当一个实例方法被调用的时候，它的方法参数和方法体内部定义的局部变量将会<strong>按照顺序被复制</strong>到局部变量表中的每一个 slot 上</li>
<li><strong>如果需要访问局部变量表中一个 64bit 的局部变量值时，只需要使用前一个索引即可</strong>。（比如：访问 long 或 doub1e 类型变量）</li>
<li>如果当前帧是由构造方法或者实例方法创建的，那么<strong>该对象引用 this 将会存放在 index 为 0 的 slot 处</strong>，其余的参数按照参数表顺序继续排列</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/slot.png" alt="slot"></p>
</li>
<li><p>Slot 的重复利用</p>
<blockquote>
<p>栈帧中的局部变量表中的槽位是可以重用的，如果一个局部变量过了其作用域，那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位，从而达到节省资源的目的</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SlotTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">localVarl</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        System.out.println(a);</span><br><span class="line">        <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">localVar2</span><span class="params">()</span> &#123;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            System.out.println(a);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//此时的就会复用a的槽位</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>静态变量与局部变量对比</p>
<ul>
<li><p>参数表分配完毕之后，再根据方法体内定义的变量的顺序和作用域分配</p>
</li>
<li><p>类变量表有两次初始化的机会，第一次是在<code>准备阶段</code>，执行系统初始化，对类变量设置零值，另一次则是在<code>初始化</code>阶段，赋予程序员在代码中定义的初始值</p>
</li>
<li><p>和类变量初始化不同，局部变量表不存在系统初始化的过程，这意味着一旦定义了局部变量则必须人为的初始化，否则无法使用</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    System. out. println(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 报错：</span></span><br><span class="line">java: 可能尚未初始化变量i</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ol>
<h4 id="4-操作数栈"><a href="#4-操作数栈" class="headerlink" title="4.操作数栈"></a>4.操作数栈</h4><ol>
<li><p>基本概念</p>
<blockquote>
<p>每一个独立的栈帧除了包含局部变量表以外，还包含一个后进先出（Last-In-First-Out）的<strong>操作数栈</strong>，也可以称之为<strong>表达式栈（Expression Stack）</strong></p>
<p><strong>操作数栈，在方法执行过程中，根据字节码指令，往栈中写入数据或提取数据，即入栈（push）和 出栈（pop）</strong></p>
<p>某些字节码指令将值压入操作数栈，其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。比如：执行复制、交换、求和等操作</p>
</blockquote>
<ul>
<li><p>操作数栈，<strong>主要用于保存计算过程的中间结果，同时作为计算过程中变量临时的存储空间</strong></p>
</li>
<li><p>操作数栈就是 JVM 执行引擎的一个工作区，当一个方法刚开始执行的时候，一个新的栈帧也会随之被创建出来，<strong>这个方法的操作数栈是空的</strong></p>
</li>
<li><p>每一个操作数栈都会拥有一个明确的栈深度用于存储数值，其所需的最大深度在编译期就定义好了，保存在方法的 Code 属性中，为 max_stack 的值</p>
</li>
<li><p>栈中的任何一个元素都是可以任意的 Java 数据类型</p>
<ul>
<li>32bit 的类型占用一个栈单位深度</li>
<li>64bit 的类型占用两个栈单位深度</li>
</ul>
</li>
<li><p>操作数栈<strong>并非采用访问索引的方式来进行数据访问</strong>的，而是只能通过标准的入栈和出栈操作来完成一次数据访问</p>
</li>
<li><p><strong>如果被调用的方法带有返回值的话，其返回值将会被压入当前栈帧的操作数栈中，并更新 PC 寄存器中下一条需要执行的字节码指令</strong></p>
</li>
<li><p>操作数栈中元素的数据类型必须与字节码指令的序列严格匹配，这由编译器在编译器期间进行验证，同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证</p>
</li>
<li><p><strong>Java 虚拟机的释引擎是基于栈的执行引擎，其中的栈指的就是操作数栈</strong></p>
</li>
</ul>
</li>
<li><p>代码举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testAddOperation</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">byte</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">15</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> i + j;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>字节码指令信息</p>
</blockquote>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">0: bipush 15</span><br><span class="line">2: istore_1</span><br><span class="line">3: bipush 8</span><br><span class="line">5: istore_2</span><br><span class="line">6:iload_1</span><br><span class="line">7:iload_2</span><br><span class="line">8:iadd</span><br><span class="line">9:istore_3</span><br><span class="line">10:return</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88.png" alt="操作数栈"></p>
</li>
</ol>
<h4 id="5-代码追踪"><a href="#5-代码追踪" class="headerlink" title="5.代码追踪"></a>5.代码追踪</h4><ol>
<li><p>代码</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testAddOperation</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">byte</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">15</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">k</span> <span class="operator">=</span> i + j;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>字节码</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">0: bipush 15</span><br><span class="line">2: istore_1</span><br><span class="line">3: bipush 8</span><br><span class="line">5: istore_2</span><br><span class="line">6:iload_1</span><br><span class="line">7:iload_2</span><br><span class="line">8:iadd</span><br><span class="line">9:istore_3</span><br><span class="line">10:return</span><br></pre></td></tr></table></figure>
</li>
<li><p>图示</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA1.png" alt="代码追踪1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA2.png" alt="代码追踪2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA3.png" alt="代码追踪3"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA4.png" alt="代码追踪4"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA5.png" alt="代码追踪5"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA6.png" alt="代码追踪6"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA7.png" alt="代码追踪7"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA8.png" alt="代码追踪8"></p>
</li>
</ol>
<h4 id="6-栈顶缓存技术"><a href="#6-栈顶缓存技术" class="headerlink" title="6.栈顶缓存技术"></a>6.栈顶缓存技术</h4><ol>
<li><p>基本概念</p>
<blockquote>
<p>基于栈式架构的虚拟机所使用的零地址指令更加紧凑，但完成一项操作的时候必然需要使用更多的入栈和出栈指令，这同时也就意味着将需要更多的指令分派（instruction dispatch）次数和内存读&#x2F;写次数，由于操作数是存储在内存中的，因此频繁地执行内存读&#x2F;写操作必然会影响执行速度</p>
<p>为了解决这个问题，HotSpot JVM 的设计者们提出了栈顶缓存（Tos，Top-of-Stack Cashing）技术，<strong>将栈顶元素全部缓存在物理 CPU 的寄存器中，以此降低对内存的读&#x2F;写次数，提升执行引擎的执行效率</strong></p>
</blockquote>
</li>
</ol>
<h4 id="7-动态链接"><a href="#7-动态链接" class="headerlink" title="7.动态链接"></a>7.动态链接</h4><ol>
<li><p>动态链接的理解</p>
<blockquote>
<p>动态链接，即<strong>指向运行时常量池的方法引用</strong></p>
<p>（动态链接、方法返回地址、附加信息） ： 有时将三者统称为称为<strong>帧数据区</strong></p>
</blockquote>
<ul>
<li>每一个栈帧内部都包含一个<strong>指向运行时常量池中该栈帧所属方法的引用</strong>。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接（Dynamic Linking）。如：invokedynamic 指令</li>
<li>在 Java 源文件被编译到字节码文件中时，所有的变量和方法引用都作为符号引用（Symbolic Reference）保存在 class 文件的常量池里。比如：描述一个方法调用了另外的其他方法时，就是通过常量池中指向方法的符号引用来表示的，那么<strong>动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5.png" alt="动态链接"></p>
</li>
<li><p>常量池</p>
<p>常量池的作用：就是为了提供一些符号和常量，便于指令的识别</p>
</li>
</ol>
<h4 id="8-方法的调用：解析和分派"><a href="#8-方法的调用：解析和分派" class="headerlink" title="8.方法的调用：解析和分派"></a>8.方法的调用：解析和分派</h4><ol>
<li><p>链接与绑定</p>
<blockquote>
<p>在 JVM 中，将符号引用转换为调用方法的直接引用与方法的绑定机制相关</p>
<p><strong>静态链接</strong>和<strong>动态链接</strong>不是名词，而是动词，这是理解的关键</p>
<p>对应的方法的绑定机制为：早期绑定（Early Binding）和晚期绑定（Late Binding）。<strong>绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程，这仅仅发生一次</strong></p>
</blockquote>
<ul>
<li><p>静态链接</p>
<blockquote>
<p>当一个字节码文件被装载进 JVM 内部时，如果<strong>被调用的目标方法在编译期可知，且运行期保持不变时</strong>，这种情况下降调用方法的符号引用转换为直接引用的过程称之为静态链接</p>
</blockquote>
</li>
<li><p>动态链接</p>
<blockquote>
<p>如果<strong>被调用的方法在编译期无法被确定下来，只能够在程序运行期将调用的方法的符号转换为直接引用</strong>，由于这种引用转换过程具备动态性，因此也被称之为动态链接</p>
</blockquote>
</li>
<li><p>早期绑定</p>
<blockquote>
<p>早期绑定就是指被调用的目标方法如果在编译期可知，且运行期保持不变时，即可将这个方法与所属的类型进行绑定，这样一来，由于明确了被调用的目标方法究竟是哪一个，因此也就可以使用静态链接的方式将符号引用转换为直接引用</p>
</blockquote>
</li>
<li><p>晚期绑定</p>
<blockquote>
<p>如果被调用的方法在编译期无法被确定下来，只能够在程序运行期根据实际的类型绑定相关的方法，这种绑定方式也就被称之为晚期绑定</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>随着高级语言的横空出世，类似于 Java 一样的基于面向对象的编程语言如今越来越多，尽管这类编程语言在语法风格上存在一定的差别，但是它们彼此之间始终保持着一个共性，那就是都支持封装、继承和多态等面向对象特性，<strong>这一类的编程语言具备多态特悄，那么自然也就具备早期绑定和晚期绑定两种绑定方式</strong></p>
<p>Java 中任何一个普通的方法其实都具备虚函数的特征，它们相当于 C++语言中的虚函数（C++中则需要使用关键字 virtual 来显式定义）。如果在 Java 程序中不希望某个方法拥有虚函数的特征时，则可以使用关键字 final 来标记这个方法</p>
</blockquote>
</li>
<li><p>非虚方法 VS 虚方法</p>
<ul>
<li><p>非虚方法</p>
<blockquote>
<p> 如果方法在编译期就确定了具体的调用版本，这个版本在运行时是不可变的。这样的方法称为非虚方法</p>
<p><strong>静态方法、私有方法、final 方法、实例构造器、父类方法都是非虚方法</strong></p>
</blockquote>
</li>
<li><p>虚方法</p>
<blockquote>
<p>其他方法称为虚方法</p>
</blockquote>
</li>
<li><p>在类加载的解析阶段就可以进行解析，如下是非虚方法举例：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Father</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">print</span><span class="params">(String str)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;father &quot;</span> + str);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">(String str)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;father&quot;</span> + str);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Son</span> <span class="keyword">extends</span> <span class="title class_">Father</span> &#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        Son.print(<span class="string">&quot;coder&quot;</span>);</span><br><span class="line">        <span class="type">Father</span> <span class="variable">fa</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Father</span>();</span><br><span class="line">        fa.show(<span class="string">&quot;atguigu.com&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p>虚拟机中提供了以下几条方法调用指令：</p>
<ul>
<li>普通调用指令：<ul>
<li><strong>invokestatic：调用静态方法，解析阶段确定唯一方法版本</strong></li>
<li><strong>invokespecial：调用方法、私有及父类方法，解析阶段确定唯一方法版本</strong></li>
<li>invokevirtual：调用所有虚方法</li>
<li>invokeinterface：调用接口方法</li>
</ul>
</li>
<li>动态调用指令：<ul>
<li>invokedynamic：动态解析出需要调用的方法，然后执行</li>
</ul>
</li>
</ul>
<blockquote>
<p>前四条指令固化在虚拟机内部，方法的调用执行不可人为干预，而 invokedynamic 指令则支持由用户确定方法版本。<strong>其中 invokestatic 指令和 invokespecial 指令调用的方法称为非虚方法，其余的（final 修饰的除外）称为虚方法</strong></p>
</blockquote>
<ul>
<li>关于 invokednamic 指令<ul>
<li><p>JVM 字节码指令集一直比较稳定，一直到 Java7 中才增加了一个 invokedynamic 指令，这是<strong>Java 为了实现「动态类型语言」支持而做的一种改进</strong></p>
</li>
<li><p>但是在 Java7 中并没有提供直接生成 invokedynamic 指令的方法，需要借助 ASM 这种底层字节码工具来产生 invokedynamic 指令。<strong>直到 Java8 的 Lambda 表达式的出现，invokedynamic 指令的生成，在 Java 中才有了直接的生成方式</strong></p>
</li>
<li><p>Java7 中增加的动态语言类型支持的本质是对 Java 虚拟机规范的修改，而不是对 Java 语言规则的修改，这一块相对来讲比较复杂，增加了虚拟机中的方法调用，最直接的受益者就是运行在 Java 平台的动态语言的编译器</p>
</li>
</ul>
</li>
<li>动态类型语言和静态类型语言<ul>
<li>动态类型语言和静态类型语言两者的区别就在于对类型的检查是在编译期还是在运行期，满足前者就是静态类型语言，反之是动态类型语言</li>
<li>静态类型语言是判断变量自身的类型信息；动态类型语言是判断变量值的类型信息，<strong>变量没有类型信息，变量值才有类型信息，这是动态语言的一个重要特征</strong></li>
</ul>
</li>
</ul>
</li>
<li><p>Java 语言中方法重写的本质</p>
<ul>
<li>找到操作数栈顶的第一个元素所执行的对象的实际类型，记作 C</li>
<li>如果在类型 C 中找到与常量中的描述符合简单名称都相符的方法，则进行访问权限校验，如果通过则返回这个方法的直接引用，查找过程结束；如果不通过，则返回 <code>java.lang.IllegalAccessError</code> 异常</li>
<li>否则，按照继承关系从下往上依次对 C 的各个父类进行第 2 步的搜索和验证过程</li>
<li>如果始终没有找到合适的方法，则抛出 <code>java.lang.AbstractMethodsrror</code> 异常</li>
</ul>
<blockquote>
<p><strong>IllegalAccessError 介绍</strong></p>
<p>程序试图访问或修改一个属性或调用一个方法，但是没有访问权限时会引起编译器异常。这个错误如果发生在运行时，就说明一个类发生了不兼容的改变</p>
</blockquote>
</li>
<li><p>方法的调用：虚方法表</p>
<blockquote>
<p>在面向对象的编程中，会很频繁的使用到动态分派，如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话就可能影响到执行效率</p>
<p>因此为了提高性能，<strong>JVM 采用在类的方法区建立一个虚方法表 （virtual method table）（非虚方法不会出现在表中）来实现。使用索引表来代替查找</strong></p>
<p>每个类中都有一个虚方法表，表中存放着各个方法的实际入口</p>
</blockquote>
<ul>
<li><p>虚方法表创建时机</p>
<blockquote>
<p><strong>虚方法表会在类加载的链接阶段被创建并开始初始化，类的变量初始值准备完成之后，JVM 会把该类的方法表也初始化完毕</strong></p>
</blockquote>
</li>
<li><p>虚方法表举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Friendly</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sayGoodbye</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dog</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">tostring</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Dog&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Cat</span> <span class="keyword">implements</span> <span class="title class_">Friendly</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">eat</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayGoodbye</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">finalize</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CockerSpaniel</span> <span class="keyword">extends</span> <span class="title class_">Dog</span> <span class="keyword">implements</span> <span class="title class_">Friendly</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.sayHello();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayGoodbye</span><span class="params">()</span> &#123;&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%99%9A%E6%96%B9%E6%B3%95%E8%A1%A82.png" alt="虚方法表2"></p>
</li>
</ul>
</li>
</ol>
<h4 id="9-方法返回地址"><a href="#9-方法返回地址" class="headerlink" title="9.方法返回地址"></a>9.方法返回地址</h4><ol>
<li><p>方法返回地址内容：存放<strong>调用该方法的 pc 寄存器的值</strong></p>
</li>
<li><p>一个方法的结束，有两种方式：</p>
<blockquote>
<p>无论通过哪种方式退出，<strong>在方法退出后都返回到该方法被调用的位置</strong></p>
<p><strong>正常完成出口和异常完成出口的区别在于：通过异常完成出口退出的不会给他的上层调用者产生任何的返回值</strong></p>
</blockquote>
<ul>
<li>正常执行完成：<strong>调用者的 pc 计数器的值作为返回地址，即调用该方法的指令的下一条指令的地址</strong></li>
<li>出现未处理的异常，非正常退出：返回地址是要通过异常表来确定，栈帧中一般不会保存这部分信息</li>
</ul>
</li>
<li><p>字节码层面解析</p>
<ul>
<li>正常完成出口：执行引擎遇到任意一个方法返回的字节码指令（return），会有返回值传递给上层的方法调用者<ul>
<li>一个方法在正常调用完成之后，究竟需要使用哪一个返回指令，还需要根据方法返回值的实际数据类型而定</li>
<li>在字节码指令中，返回指令包含 ireturn（当返回值是 boolean，byte，char，short 和 int 类型时使用），lreturn（Long 类型），freturn（Float 类型），dreturn（Double 类型），areturn</li>
<li>另外还有 return 指令声明为 void 的方法，如<strong>实例初始化方法，类和接口的初始化方法</strong></li>
</ul>
</li>
<li>异常完成出口：在方法执行过程中遇到异常（Exception），并且这个异常没有在方法内进行处理，也就是只要在本方法的异常表中没有搜索到匹配的异常处理器，就会导致方法退出</li>
</ul>
</li>
<li><p>方法退出的本质</p>
<blockquote>
<p>本质上，方法的退出就是当前栈帧出栈的过程。此时，需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置 PC 寄存器值等，让调用者方法继续执行下去</p>
</blockquote>
</li>
</ol>
<h4 id="10-一些附加信息"><a href="#10-一些附加信息" class="headerlink" title="10.一些附加信息"></a>10.一些附加信息</h4><p>栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息</p>
<p>例如：对程序调试提供支持的信息</p>
<h4 id="11-栈相关面试题"><a href="#11-栈相关面试题" class="headerlink" title="11.栈相关面试题"></a>11.栈相关面试题</h4><ol>
<li><p>举例栈溢出的情况？（StackOverflowError）</p>
<blockquote>
<p>通过 -Xss 设置栈的大小</p>
</blockquote>
</li>
<li><p>调整栈大小，就能保证不出现溢出么？</p>
<blockquote>
<p>不能保证不溢出</p>
</blockquote>
</li>
<li><p>分配的栈内存越大越好么？</p>
<blockquote>
<p>不是，一定时间内降低了 OOM 概率，但是会挤占其它的线程空间，因为整个空间是有限的</p>
</blockquote>
</li>
<li><p>垃圾回收是否涉及到虚拟机栈？</p>
<blockquote>
<p>不会</p>
</blockquote>
</li>
<li><p>方法中定义的局部变量是否线程安全？</p>
<blockquote>
<p>具体问题具体分析。如果对象是在内部产生，并在内部消亡，没有返回到外部，那么它就是线程安全的，反之则是线程不安全的</p>
</blockquote>
</li>
<li><p>内存各部分的 GC 和 Error（如 OOM、StackOverFlow 等） 情况对比</p>
<table>
<thead>
<tr>
<th align="left">运行时数据区</th>
<th align="left">是否存在 Error</th>
<th align="left">是否存在 GC</th>
</tr>
</thead>
<tbody><tr>
<td align="left">程序计数器</td>
<td align="left">否</td>
<td align="left">否</td>
</tr>
<tr>
<td align="left">虚拟机栈</td>
<td align="left">是（SOE）</td>
<td align="left">否</td>
</tr>
<tr>
<td align="left">本地方法栈</td>
<td align="left">是</td>
<td align="left">否</td>
</tr>
<tr>
<td align="left">方法区</td>
<td align="left">是（OOM）</td>
<td align="left">是</td>
</tr>
<tr>
<td align="left">堆</td>
<td align="left">是</td>
<td align="left">是</td>
</tr>
</tbody></table>
</li>
</ol>
<h3 id="6-本地方法接口"><a href="#6-本地方法接口" class="headerlink" title="6.本地方法接口"></a>6.本地方法接口</h3><h4 id="1-什么是本地方法"><a href="#1-什么是本地方法" class="headerlink" title="1.什么是本地方法"></a>1.什么是本地方法</h4><ol>
<li><p>定义</p>
<blockquote>
<p>简单地讲，<strong>一个 Native Method 是一个 Java 调用非 Java 代码的接囗</strong></p>
<p>一个 Native Method 是一个 Java 方法：该方法的实现由非 Java 语言实现，比如 C</p>
<p>这个特征并非 Java 所特有，很多其它的编程语言都有这一机制，比如在 C++中，你可以用 extern “c” 告知 c++编译器去调用一个 c 的函数</p>
</blockquote>
<ul>
<li>在定义一个 native method 时，并不提供实现体（有些像定义一个 Java interface），因为其实现体是由非 java 语言在外面实现的</li>
<li>本地接口的作用是融合不同的编程语言为 Java 所用，它的初衷是融合 C&#x2F;C++程序</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/native%20method%E4%BD%8D%E7%BD%AE.png" alt="native method位置"></p>
</li>
<li><p>举例</p>
<blockquote>
<p>标识符 native 可以与其它 java 标识符连用，但是 abstract 除外</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IHaveNatives</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title function_">methodNative1</span><span class="params">(<span class="type">int</span> x)</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">static</span> <span class="type">long</span> <span class="title function_">methodNative2</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">native</span> <span class="keyword">synchronized</span> <span class="type">float</span> <span class="title function_">methodNative3</span><span class="params">(Object o)</span>;</span><br><span class="line">    <span class="keyword">native</span> <span class="keyword">void</span> <span class="title function_">methodNative4</span><span class="params">(<span class="type">int</span>[] ary)</span> <span class="keyword">throws</span> Exception;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="2-为什么使用-native-method"><a href="#2-为什么使用-native-method" class="headerlink" title="2.为什么使用 native method"></a>2.为什么使用 <code>native method</code></h4><blockquote>
<p>Java 使用起来非常方便，然而有些层次的任务用 Java 实现起来不容易，或者我们对程序的效率很在意时，问题就来了</p>
</blockquote>
<ol>
<li><p>与 Java 环境的交互</p>
<blockquote>
<p><strong>有时 Java 应用需要与 Java 外面的环境交互，这是本地方法存在的主要原因</strong></p>
<p> Java 需要与一些底层系统，如操作系统或某些硬件交换信息时的情况</p>
<p>本地方法正是这样一种交流机制：它为我们提供了一个非常简洁的接口，而且我们无需去了解 Java 应用之外的繁琐的细节</p>
</blockquote>
</li>
<li><p>与操作系统的交互</p>
<blockquote>
<p>JVM 支持着 Java 语言本身和运行时库，它是 Java 程序赖以生存的平台，它由一个解释器（解释字节码）和一些连接到本地代码的库组成</p>
<p>然而不管怎样，它毕竟不是一个完整的系统，它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。<strong>通过使用本地方法，我们得以用 Java 实现了 jre 的与底层系统的交互，甚至 JVM 的一些部分就是用 c 写的</strong></p>
<p>还有，如果我们要使用一些 Java 语言本身没有提供封装的操作系统的特性时，我们也需要使用本地方法</p>
</blockquote>
</li>
<li><p>Sun’s Java</p>
<blockquote>
<p><strong>Sun 的解释器是用 C 实现的，这使得它能像一些普通的 C 一样与外部交互</strong></p>
<p>jre 大部分是用 Java 实现的，它也通过一些本地方法与外界交互。例如：类 java.lang.Thread 的 setPriority()方法是用 Java 实现的，但是它实现调用的是该类里的本地方法 setPriority()。这个本地方法是用 C 实现的，并被植入 JVM 内部，在 Windows 95 的平台上，这个本地方法最终将调用 Win32 setPriority() ApI</p>
<p>这是一个本地方法的具体实现由 JVM 直接提供，更多的情况是本地方法由外部的动态链接库（external dynamic link library）提供，然后被 JVM 调用</p>
</blockquote>
</li>
<li><p>现状</p>
<blockquote>
<p><strong>目前该方法使用的越来越少了，除非是与硬件有关的应用</strong></p>
<p>比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备</p>
<p>在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达，比如可以使用 Socket 通信，也可以使用 Web Service 等等，不多做介绍</p>
</blockquote>
</li>
</ol>
<h3 id="7-本地方法栈"><a href="#7-本地方法栈" class="headerlink" title="7.本地方法栈"></a>7.本地方法栈</h3><h4 id="1-相关概念"><a href="#1-相关概念" class="headerlink" title="1.相关概念"></a>1.相关概念</h4><ol>
<li><p>基本概念</p>
<ul>
<li><strong>本地方法栈，也是线程私有的</strong></li>
<li>允许被实现成固定或者是可动态扩展的内存大小（在内存溢出方面是相同的）<ul>
<li>如果线程请求分配的栈容量超过本地方法栈允许的最大容量，Java 虚拟机将会抛出一个 StackOverflowError 异常</li>
<li>如果本地方法栈可以动态扩展，并且在尝试扩展的时候无法申请到足够的内存，或者在创建新的线程时没有足够的内存去创建对应的本地方法栈，那么 Java 虚拟机将会抛出一个 OutOfMemoryError 异常</li>
</ul>
</li>
<li>本地方法是使用 C 语言实现的</li>
<li>它的具体做法是 Native Method Stack 中登记 native 方法，在 Execution Engine 执行时加载本地方法库</li>
</ul>
</li>
<li><p>作用</p>
<blockquote>
<p><strong>Java 虚拟机栈于管理 Java 方法的调用，而本地方法栈用于管理本地方法的调用</strong></p>
</blockquote>
</li>
</ol>
<h4 id="2-深入理解"><a href="#2-深入理解" class="headerlink" title="2.深入理解"></a>2.深入理解</h4><ol>
<li><p>在内存结构中的位置</p>
<blockquote>
<p><strong>当某个线程调用一个本地方法时，它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%8E%A5%E5%8F%A3.png" alt="本地方法接口"></p>
<ul>
<li>本地方法可以通过本地方法接口来<strong>访问虚拟机内部的运行时数据区</strong></li>
<li>它甚至可以直接使用本地处理器中的<strong>寄存器</strong></li>
<li><strong>直接从本地内存的堆中分配任意数量的内存</strong></li>
</ul>
</li>
<li><p><strong>并不是所有的 JVM 都支持本地方法。因为 Java 虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等</strong></p>
<blockquote>
<p>如果 JVM 产品不打算支持 native 方法，也可以无需实现本地方法栈</p>
<p><strong>在 Hotspot JVM 中，直接将本地方法栈和虚拟机栈合二为一</strong></p>
</blockquote>
</li>
</ol>
<h3 id="8-堆"><a href="#8-堆" class="headerlink" title="8.堆"></a>8.堆</h3><h4 id="1-堆的核心概述"><a href="#1-堆的核心概述" class="headerlink" title="1.堆的核心概述"></a>1.堆的核心概述</h4><ol>
<li><p>堆在内存结构中的位置</p>
<ul>
<li>堆针对一个 JVM 进程来说是唯一的，也就是一个进程只有一个 JVM，但是进程包含多个线程，他们是共享同一堆空间的</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86%E5%9C%A8%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE.png" alt="堆在内存结构中的位置"></p>
<ul>
<li><p>一个 JVM 实例只存在一个堆内存，堆也是 Java 内存管理的核心区域</p>
</li>
<li><p>Java 堆区在 JVM 启动的时候即被创建，其空间大小也就确定了。是 JVM 管理的最大一块内存空间</p>
</li>
</ul>
</li>
<li><p>堆大小可调节</p>
<ul>
<li>《Java 虚拟机规范》规定，<strong>堆可以处于物理上不连续的内存空间中，但在逻辑上它应该被视为连续的</strong></li>
<li><strong>所有的线程共享 Java 堆，在这里还可以划分线程私有的缓冲区（Thread Local Allocation Buffer，TLAB）</strong></li>
<li>《Java 虚拟机规范》中对 Java 堆的描述是：<strong>所有的对象实例以及数组都应当在运行时分配在堆上</strong></li>
<li>数组和对象可能永远不会存储在栈上，因为栈帧中保存引用，这个引用指向对象或者数组在堆中的位置</li>
<li><strong>在方法结束后，堆中的对象不会马上被移除，仅仅在垃圾收集的时候才会被移除</strong></li>
<li><strong>堆，是 GC（Garbage Collection，垃圾收集器）执行垃圾回收的重点区域</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86%E6%A0%88%E6%96%B9%E6%B3%95%E5%8C%BA.png" alt="堆栈方法区"></p>
</li>
<li><p>堆中内存结构细分</p>
<blockquote>
<p>重点在<strong>方法区</strong>的叫法不同：Java7 指永久区；Java8 指元空间</p>
</blockquote>
<ul>
<li><p>Java7 及之前堆内存逻辑上分为三部分：新生区 + 养老区 + <strong>永久区</strong></p>
<ul>
<li>Young Generation Space 新生区 Young &#x2F; New 又被划分为 Eden 区和 Survivor 区</li>
<li>Tenure generation space 养老区 Old &#x2F; Tenure</li>
<li><strong>Permanent Space 永久区 Perm</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86JDK7.png" alt="堆JDK7"></p>
</li>
<li><p>Java8 及之后堆内存逻辑上分为三部分：新生区 + 养老区 + <strong>元空间</strong></p>
<ul>
<li>Young Generation Space 新生区 Young &#x2F; New 又被划分为 Eden 区和 Survivor 区</li>
<li>Tenure generation space 养老区 Old &#x2F; Tenure</li>
<li><strong>Meta Space 元空间 Meta</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86JDK8.png" alt="堆JDK8"></p>
</li>
</ul>
</li>
</ol>
<h4 id="2-设置堆内存大小和-OOM"><a href="#2-设置堆内存大小和-OOM" class="headerlink" title="2.设置堆内存大小和 OOM"></a>2.设置堆内存大小和 OOM</h4><ol>
<li><p>堆空间大小的设置</p>
<ul>
<li><p>Java 堆区用于存储 Java 对象实例，那么堆的大小在 JVM 启动时就已经设定好了，可以通过选项 <code>-Xmx</code> 和 <code>-Xms</code> 来进行设置</p>
<ul>
<li><code>-Xms</code> 用于表示堆区的初始内存，等价于 <code>-XX:InitialHeapSize</code></li>
<li><code>-Xmx</code> 用于表示堆区的最大内存，等价于 <code>-XX:MaxHeapSize</code></li>
</ul>
</li>
<li><p>一旦堆区中的内存大小超过 <code>-Xmx</code> 所指定的最大内存时，将会抛出 OutOfMemoryError 异常</p>
</li>
<li><p>通常会将 <code>-Xms</code> 和 <code>-Xmx</code> 两个参数配置相同的值，其目的是<strong>为了能够在 ava 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小，从而提高性能</strong></p>
</li>
<li><p>默认情况下大小</p>
<ul>
<li>初始内存大小：物理电脑内存大小 &#x2F; 64</li>
<li>最大内存大小：物理电脑内存大小 &#x2F; 4</li>
</ul>
</li>
</ul>
</li>
<li><p>OOM 举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[]args)</span>&#123;</span><br><span class="line">    ArrayList&lt;Picture&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">20</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        list.add(<span class="keyword">new</span> <span class="title class_">Picture</span>(<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">1024</span>*<span class="number">1024</span>)));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// output</span></span><br><span class="line">Exception in thread <span class="string">&quot;main&quot;</span> java.lang.OutofMemoryError: Java heap space</span><br><span class="line">    at com.atguigu. java.Picture.&lt;init&gt;(OOMTest. java:<span class="number">25</span>)</span><br><span class="line">    at com.atguigu.java.O0MTest.main(OOMTest.java:<span class="number">16</span>)</span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="3-年轻代与老年代"><a href="#3-年轻代与老年代" class="headerlink" title="3.年轻代与老年代"></a>3.年轻代与老年代</h4><ol>
<li><p>存储在 JVM 中的 Java 对象可以被划分为两类：</p>
<ul>
<li>一类是生命周期较短的瞬时对象，这类对象的创建和消亡都非常迅速</li>
<li>另外一类对象的生命周期却非常长，在某些极端的情况下还能够与 JVM 的生命周期保持一致</li>
</ul>
</li>
<li><p>堆内存细分</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86%E5%86%85%E5%AD%98%E7%BB%86%E5%88%86.png" alt="堆内存细分"></p>
<ul>
<li><p>配置新生代与老年代在堆结构的占比<strong>（这参数开发中一般不会调）</strong></p>
<ul>
<li>默认<code>-XX:NewRatio=2</code>，表示新生代占 1，老年代占 2，新生代占整个堆的 1&#x2F;3</li>
<li>可以修改<code>-XX:NewRatio=4</code>，表示新生代占 1，老年代占 4，新生代占整个堆的 1&#x2F;5</li>
</ul>
</li>
<li><p>在 HotSpot 中，Eden 空间和另外两个 survivor 空间默认所占的比例是 8：1：1<strong>（这参数开发中一般不会调）</strong></p>
<blockquote>
<p>开发人员可以通过选项 <code>-xx:SurvivorRatio</code> 调整这个空间比例</p>
<p>比如<code>-xx:SurvivorRatio=8</code>（表示 8：1：1）</p>
</blockquote>
</li>
<li><p>几乎所有的 Java 对象都是在 Eden 区被 new 出来的。绝大部分的 Java 对象的销毁都在新生代进行</p>
<blockquote>
<p>IBM 公司的专门研究表明，新生代中 80% 的对象都是<strong>朝生夕死</strong>的</p>
</blockquote>
</li>
<li><p>可以使用选项 <code>-Xmn</code> 设置新生代最大内存大小<strong>（这参数开发中一般不会调）</strong></p>
</li>
</ul>
</li>
</ol>
<h4 id="4-图解对象分配过程"><a href="#4-图解对象分配过程" class="headerlink" title="4.图解对象分配过程"></a>4.图解对象分配过程</h4><blockquote>
<p>为新对象分配内存是一件非常严谨和复杂的任务，JVM 的设计者们不仅需要考虑内存如何分配、在哪里分配等问题，并且由于内存分配算法与内存回收算法密切相关，所以还需要考虑 GC 执行完内存回收后是否会在内存空间中产生内存碎片</p>
</blockquote>
<ol>
<li><p>new 的对象先放伊甸园区。此区有大小限制</p>
</li>
<li><p>当伊甸园的空间填满时，程序又需要创建对象，JVM 的垃圾回收器将对伊甸园区进行垃圾回收（MinorGC &#x2F; YGC），将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区</p>
</li>
<li><p>然后将伊甸园中的剩余对象移动到幸存者 0 区。</p>
</li>
<li><p>如果再次触发垃圾回收，此时上次幸存下来的放到幸存者 0 区的，如果没有回收，就会放到幸存者 1 区</p>
</li>
<li><p>如果再次经历垃圾回收，此时会重新放回幸存者 0 区，接着再去幸存者 1 区</p>
</li>
<li><p>啥时候能去养老区呢？可以设置次数。默认是 15 次</p>
<ul>
<li>可以设置参数：<code>-Xx:MaxTenuringThreshold= N</code>进行设置</li>
</ul>
</li>
<li><p>在养老区，相对悠闲。当养老区内存不足时，再次触发 GC：Major GC，进行养老区的内存清理</p>
</li>
<li><p>若养老区执行了 Major GC 之后，发现依然无法进行对象的保存，就会产生 OOM 异常</p>
</li>
</ol>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E8%BF%87%E7%A8%8B01.png" alt="对象分配过程01"></p>
<blockquote>
<p>流程图展示如下</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E8%BF%87%E7%A8%8B02.png" alt="对象分配过程02"></p>
<blockquote>
<p>注意事项：</p>
<ul>
<li>针对幸存者 s0，s1 区的总结：复制之后有交换，<strong>谁满谁是 from，谁空谁是 to</strong></li>
<li><strong>survivor 区满不会触发 YGC（只有 Eden 满会触发 YGC）</strong></li>
<li><strong>关于垃圾回收：频繁在新生区收集，很少在老年代收集，几乎不在永久代和元空间进行收集</strong></li>
</ul>
</blockquote>
<blockquote>
<p><strong>常用调优工具（在 JVM 下篇：性能监控与调优篇会详细介绍）</strong></p>
<ul>
<li>JDK 命令行</li>
<li>Eclipse:Memory Analyzer Tool</li>
<li>Jconsole</li>
<li>VisualVM</li>
<li>Jprofiler</li>
<li>Java Flight Recorder</li>
<li>GCViewer</li>
<li>GC Easy</li>
</ul>
</blockquote>
<h4 id="5-Minor-GC（YGC）、Major-GC、Full-GC（FGC）"><a href="#5-Minor-GC（YGC）、Major-GC、Full-GC（FGC）" class="headerlink" title="5.Minor GC（YGC）、Major GC、Full GC（FGC）"></a>5.Minor GC（YGC）、Major GC、Full GC（FGC）</h4><blockquote>
<p>JVM 在进行 GC 时，并非每次都对上面三个内存区域一起回收的，大部分时候回收的都是指新生代</p>
</blockquote>
<ol>
<li><p>GC 按照回收区域分两种类型：部分收集、整堆收集</p>
<ul>
<li>部分收集：不是完整收集整个 Java 堆的垃圾收集。其中又分为：<ul>
<li>新生代收集（Minor GC &#x2F; Young GC）：只是新生代的垃圾收集</li>
<li>老年代收集（Major GC &#x2F; Old GC）：只是老年代的圾收集<ul>
<li>目前，只有 CMSGC 会有单独收集老年代的行为</li>
<li><strong>注意，很多时候 Major GC 会和 Full GC 混淆使用，需要具体分辨是老年代回收还是整堆回收</strong></li>
</ul>
</li>
<li>混合收集（MixedGC）：收集整个新生代以及部分老年代的垃圾收集（目前，只有 G1 GC 会有这种行为）</li>
</ul>
</li>
<li>整堆收集（Full GC）：<strong>收集整个 java 堆和方法区的垃圾收集</strong></li>
</ul>
</li>
<li><p>年轻代 GC 触发机制</p>
<ul>
<li><p>当年轻代空间不足时，就会触发 MinorGC，这里的年轻代满指的是 Eden 代满，Survivor 满不会引发 GC（每次 Minor GC 会清理年轻代的内存）</p>
</li>
<li><p>因为<strong>Java 对象大多都具备朝生夕灭的特性</strong>，所以 Minor GC 非常频繁，一般回收速度也比较快。这一定义既清晰又易于理解</p>
</li>
<li><p>Minor GC 会引发 STW，暂停其它用户的线程，等垃圾回收结束，用户线程才恢复运行</p>
</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/YGC%E8%A7%A6%E5%8F%91%E6%9C%BA%E5%88%B6.png" alt="YGC触发机制"></p>
</li>
<li><p>老年代 GC 触发机制</p>
<ul>
<li><p>指发生在老年代的 GC，对象从老年代消失时，即发生 <code>Major GC</code> 或 <code>Full GC</code> </p>
</li>
<li><p><strong>出现了 Major GC，经常会伴随至少一次的 Minor GC（但非绝对的，在 Paralle1 Scavenge 收集器的收集策略里就有直接进行 MajorGC 的策略选择过程）</strong></p>
<p>也就是在老年代空间不足时，会先尝试触发 Minor GC。如果之后空间还不足，则触发 Major GC</p>
</li>
<li><p><strong>Major GC 的速度一般会比 Minor GC 慢 10 倍以上，STW 的时间更长</strong></p>
</li>
<li><p>如果 Major GC 后，内存还不足，就报 OOM 错误</p>
</li>
</ul>
</li>
<li><p>Full GC 触发机制（后面性能调优细讲，触发 Full GC 执行的情况有如下五种：）</p>
<blockquote>
<p><strong>Full GC 是开发或调优中尽量要避免的。这样暂时时间会短一些</strong></p>
</blockquote>
<ul>
<li>调用 <code>System.gc()</code> 时，系统建议执行 Full GC，但是不必然执行</li>
<li>老年代空间不足</li>
<li>方法区空间不足</li>
<li><strong>通过 Minor GC 后进入老年代的平均大小大于老年代的可用内存</strong></li>
<li>由 Eden 区、survivor space0（From Space）区向 survivor space1（To Space）区复制时，对象大小大于 To Space 可用内存，则把该对象转存到老年代，<strong>且老年代的可用内存小于该对象大小</strong></li>
</ul>
</li>
</ol>
<h4 id="6-堆空间分代思想"><a href="#6-堆空间分代思想" class="headerlink" title="6.堆空间分代思想"></a>6.堆空间分代思想</h4><blockquote>
<p>为什么要把 Java 堆分代？不分代就不能正常工作了吗？</p>
<p>经研究，不同对象的生命周期不同。70%-99%的对象是临时对象</p>
<p><strong>分代可以优化 GC 性能</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK7.png" alt="JDK7"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK8.png" alt="JDK8"></p>
<h4 id="7-内存分配策略"><a href="#7-内存分配策略" class="headerlink" title="7.内存分配策略"></a>7.内存分配策略</h4><blockquote>
<p>如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活，并且能被 Survivor 容纳的话，将被移动到 survivor 空间中，并将对象年龄设为 1。对象在 survivor 区中每熬过一次 MinorGC，年龄就增加 1 岁，当它的年龄增加到一定程度（默认为 15 岁，其实每个 JVM、每个 GC 都有所不同）时，就会被晋升到老年代</p>
<p>对象晋升老年代的年龄阀值，可以通过选项<code>-XX:MaxTenuringThreshold</code>来设置</p>
<p>针对不同年龄段的对象分配原则如下所示：</p>
<ul>
<li>优先分配到 Eden</li>
<li>大对象直接分配到老年代（尽量避免程序中出现过多的大对象）</li>
<li>长期存活的对象分配到老年代</li>
<li>动态对象年龄判断：如果 survivor 区中相同年龄的所有对象大小的总和大于 Survivor 空间的一半，年龄大于或等于该年龄的对象可以直接进入老年代，无须等到 <code>MaxTenuringThreshold</code> 中要求的年龄。</li>
<li>空间分配担保： <code>-XX:HandlePromotionFailure</code></li>
</ul>
</blockquote>
<h4 id="8-为对象分配内存：TLAB"><a href="#8-为对象分配内存：TLAB" class="headerlink" title="8.为对象分配内存：TLAB"></a>8.为对象分配内存：TLAB</h4><ol>
<li><p>为什么有 TLAB</p>
<ul>
<li><p>堆区是线程共享区域，任何线程都可以访问到堆区中的共享数据</p>
</li>
<li><p>对象实例的创建在 JVM 中非常频繁，因此在并发环境下从堆区中划分内存空间是线程不安全的</p>
</li>
<li><p>为避免多个线程操作同一地址，需要使用加锁等机制，进而影响分配速度</p>
</li>
</ul>
</li>
<li><p>什么是 TLAB</p>
<ul>
<li><p>从内存模型而不是垃圾收集的角度，对 Eden 区域继续进行划分，<strong>JVM 为每个线程分配了一个私有缓存区域</strong>，它包含在 Eden 空间内</p>
</li>
<li><p>多线程同时分配内存时，使用 TLAB 可以避免一系列的非线程安全问题，同时还能够提升内存分配的吞吐量，因此我们可以将这种内存分配方式称之为<strong>快速分配策略</strong></p>
</li>
<li><p>所有 OpenJDK 衍生出来的 JVM 都提供了 TLAB 的设计</p>
</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/TLAB.png" alt="TLAB"></p>
</li>
<li><p>TLAB 使用说明</p>
<ul>
<li><p>尽管不是所有的对象实例都能够在 TLAB 中成功分配内存（TLAB 太小），但<strong>JVM 确实是将 TLAB 作为内存分配的首选</strong></p>
</li>
<li><p>在程序中，开发人员可以通过选项 <code>-XX:UseTLAB</code> 设置是否开启 TLAB 空间</p>
</li>
<li><p>默认情况下，<strong>TLAB 空间的内存非常小，仅占有整个 Eden 空间的 1%</strong></p>
<p>可以通过选项  <code>-XX:TLABWasteTargetPercent</code> 设置 TLAB 空间所占用 Eden 空间的百分比大小</p>
</li>
<li><p>一旦对象在 TLAB 空间分配内存失败时，JVM 就会尝试着通过使用加锁机制确保数据操作的原子性，从而直接在 Eden 空间中分配内存</p>
</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/TLAB%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png" alt="TLAB分配流程"></p>
</li>
</ol>
<h4 id="9-小结：堆空间的参数设置"><a href="#9-小结：堆空间的参数设置" class="headerlink" title="9.小结：堆空间的参数设置"></a>9.小结：堆空间的参数设置</h4><ol>
<li><p><a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html">官网地址</a></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 详细的参数内容会在JVM下篇：性能监控与调优篇中进行详细介绍，这里先熟悉下</span></span><br><span class="line">-XX:+PrintFlagsInitial  <span class="comment">//查看所有的参数的默认初始值</span></span><br><span class="line">-XX:+PrintFlagsFinal  <span class="comment">//查看所有的参数的最终值（可能会存在修改，不再是初始值）</span></span><br><span class="line">-Xms  <span class="comment">//初始堆空间内存（默认为物理内存的1/64）</span></span><br><span class="line">-Xmx  <span class="comment">//最大堆空间内存（默认为物理内存的1/4）</span></span><br><span class="line">-Xmn  <span class="comment">//设置新生代的大小。（初始值及最大值）</span></span><br><span class="line">-XX:NewRatio  <span class="comment">//配置新生代与老年代在堆结构的占比</span></span><br><span class="line">-XX:SurvivorRatio  <span class="comment">//设置新生代中Eden和S0/S1空间的比例</span></span><br><span class="line">-XX:MaxTenuringThreshold  <span class="comment">//设置新生代垃圾的最大年龄</span></span><br><span class="line">-XX:+PrintGCDetails <span class="comment">//输出详细的GC处理日志</span></span><br><span class="line"><span class="comment">//打印gc简要信息：①-Xx：+PrintGC ② - verbose:gc</span></span><br><span class="line">-XX:HandlePromotionFalilure：<span class="comment">//是否设置空间分配担保</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>空间分配担保</p>
<ul>
<li>在发生 Minor GC 之前，<strong>虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间</strong><ul>
<li>如果大于，则此次 Minor GC 是安全的</li>
<li>如果小于，则虚拟机会查看 <code>-XX:HandlePromotionFailure</code> 设置值是否允担保失败<ul>
<li>如果<code>HandlePromotionFailure=true</code>，那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小<ul>
<li>如果大于，则尝试进行一次 Minor GC，但这次 Minor GC 依然是有风险的</li>
<li>如果小于，则改为进行一次 Full GC</li>
</ul>
</li>
<li>如果<code>HandlePromotionFailure=false</code>，则改为进行一次 Full Gc</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>在 JDK6 Update24 之后，HandlePromotionFailure 参数不会再影响到虚拟机的空间分配担保策略，观察 openJDK 中的源码变化，虽然源码中还定义了 HandlePromotionFailure 参数，但是在代码中已经不会再使用它。JDK6 Update 24 之后的规则变为<strong>只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行 Minor GC，否则将进行 Full GC</strong>（相当于 <code>HandlePromotionFailure</code> 设置为 true）</p>
</blockquote>
</li>
</ol>
<h4 id="10-堆是分配对象的唯一选择吗？"><a href="#10-堆是分配对象的唯一选择吗？" class="headerlink" title="10.堆是分配对象的唯一选择吗？"></a>10.堆是分配对象的唯一选择吗？</h4><blockquote>
<p>在《深入理解 Java 虚拟机》中关于 Java 堆内存有这样一段描述：</p>
<p>随着 JIT 编译期的发展与<strong>逃逸分析技术</strong>逐渐成熟，<strong>栈上分配</strong>、<strong>标量替换优化技术</strong>将会导致一些微妙的变化，所有的对象都分配到堆上也渐渐变得不那么绝对了</p>
<p>在 Java 虚拟机中，对象是在 Java 堆中分配内存的，这是一个普遍的常识。但是，有一种特殊情况，那就是<strong>如果经过逃逸分析（Escape Analysis）后发现，一个对象并没有逃逸出方法的话，那么就可能被优化成栈上分配</strong>。这样就无需在堆上分配内存，也无须进行垃圾回收了。这也是最常见的堆外存储技术</p>
<p>此外，前面提到的基于 OpenJDK 深度定制的 TaoBaoVM，其中创新的 GCIH（GC invisible heap）技术实现 off-heap，将生命周期较长的 Java 对象从 heap 中移至 heap 外，并且 GC 不能管理 GCIH 内部的 Java 对象，以此达到降低 GC 的回收频率和提升 GC 的回收效率的目的</p>
</blockquote>
<ol>
<li><p>逃逸分析概述</p>
<ul>
<li><p>逃逸分析是一种可以有效减少 Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法</p>
<p>通过逃逸分析，Java Hotspot 编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上</p>
</li>
<li><p>逃逸分析的基本行为就是分析对象动态作用域：</p>
<ul>
<li>当一个对象在方法中被定义后，对象只在方法内部使用，则认为没有发生逃逸</li>
<li>当一个对象在方法中被定义后，它被外部方法所引用，则认为发生逃逸。例如作为调用参数传递到其他地方中</li>
</ul>
</li>
</ul>
</li>
<li><p>逃逸分析举例</p>
<blockquote>
<p>举例一：没有发生<code>逃逸</code></p>
<p>没有发生逃逸的对象，则可以分配到栈上，随着方法执行的结束，栈空间就被移除，每个栈里面包含了很多栈帧</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">my_method</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">V</span> <span class="variable">v</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">V</span>();</span><br><span class="line">    <span class="comment">// use v</span></span><br><span class="line">    <span class="comment">// ....</span></span><br><span class="line">    v = <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>举例二：<code>逃逸</code>优化为<code>不逃逸</code></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 有逃逸</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> StringBuffer <span class="title function_">createStringBuffer</span><span class="params">(String s1, String s2)</span> &#123;</span><br><span class="line">    <span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line">    sb.append(s1);</span><br><span class="line">    sb.append(s2);</span><br><span class="line">    <span class="keyword">return</span> sb;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 无逃逸</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">createStringBuffer</span><span class="params">(String s1, String s2)</span> &#123;</span><br><span class="line">    <span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line">    sb.append(s1);</span><br><span class="line">    sb.append(s2);</span><br><span class="line">    <span class="keyword">return</span> sb.toString();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>距离三：<code>逃逸</code>情况列举（被外部方法所引用，则认为发生逃逸）</p>
<p>返回值、为成员属性赋值、引用成员变量的值</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EscapeAnalysis</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> EscapeAnalysis obj;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 方法返回EscapeAnalysis对象，发生逃逸</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> EscapeAnalysis <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> obj == <span class="literal">null</span> ? <span class="keyword">new</span> <span class="title class_">EscapeAnalysis</span>() : obj;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 为成员属性赋值，发生逃逸</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setObj</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.obj = <span class="keyword">new</span> <span class="title class_">EscapeAnalysis</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 对象的作用于仅在当前方法中有效，没有发生逃逸</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">useEscapeAnalysis</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">EscapeAnalysis</span> <span class="variable">e</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EscapeAnalysis</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 引用成员变量的值，发生逃逸</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">useEscapeAnalysis2</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">EscapeAnalysis</span> <span class="variable">e</span> <span class="operator">=</span> getInstance();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>逃逸分析的参数设置</p>
<blockquote>
<p>在 JDK 7 版本及之后，<strong>HotSpot 中默认就已经开启了逃逸分析</strong></p>
</blockquote>
<ul>
<li><p>如果使用的是较早的版本，开发人员则可以通过：</p>
<ul>
<li><p>选项 <code>-XX:+DoEscapeAnalysis</code> 显式开启逃逸分析</p>
</li>
<li><p>通过选项 <code>-XX:+PrintEscapeAnalysis</code> 查看逃逸分析的筛选结果</p>
</li>
</ul>
</li>
<li><p><strong>结论</strong>：<strong>开发中能使用局部变量的，就不要使用在方法外定义</strong></p>
</li>
</ul>
</li>
<li><p>逃逸分析：代码优化</p>
<ul>
<li><p>使用逃逸分析，编译器可以对代码做如下优化：</p>
<ul>
<li>栈上分配：将堆分配转化为栈分配。如果一个对象在子程序中被分配，要使指向该对象的指针永远不会发生逃逸，对象可能是栈上分配的候选，而不是堆上分配</li>
<li>同步省略：如果一个对象被发现只有一个线程被访问到，那么对于这个对象的操作可以不考虑同步</li>
<li>分离对象或标量替换：有的对象可能不需要作为一个连续的内存结构存在也可以被访问到，那么对象的部分（或全部）可以不存储在内存，而是存储在 CPU 寄存器中</li>
</ul>
</li>
<li><p>栈上分析：</p>
<blockquote>
<p>JIT 编译器在编译期间根据逃逸分析的结果，发现如果一个对象并没有逃逸出方法的话，就可能被优化成栈上分配。分配完成后，继续在调用栈内执行，最后线程结束，栈空间被回收，局部变量对象也被回收，这样就无须进行垃圾回收了</p>
<p><strong>常见的栈上分配的场景：</strong></p>
<p>给成员变量赋值、方法返回值、实例引用传递</p>
</blockquote>
</li>
<li><p>同步省略：</p>
<blockquote>
<p>线程同步的代价是相当高的，<strong>同步的后果是降低并发性和性能</strong></p>
<p>在动态编译同步块的时候，JIT 编译器可以<strong>借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程</strong></p>
<p>如果没有，那么 JIT 编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫<strong>同步省略</strong>，也叫<strong>锁消除</strong></p>
<p>举例如下：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">f</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">hellis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">synchronized</span>(hellis) &#123;</span><br><span class="line">        System.out.println(hellis);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//代码中对 hellis 这个对象加锁，但是 hellis 对象的生命周期只在 f()方法中，并不会被其他线程所访问到，所以在 JIT 编译阶段就会被优化掉，优化成：</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">f</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Object</span> <span class="variable">hellis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">	System.out.println(hellis);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>标量替换</p>
<blockquote>
<p><strong>标量（scalar）是指一个无法再分解成更小的数据的数据。Java 中的原始数据类型就是标量</strong></p>
<p>相对的，那些<strong>还可以分解的数据叫做聚合量（Aggregate）</strong>，<strong>Java 中的对象就是聚合量</strong>，因为他可以分解成其他聚合量和标量</p>
<p>在 JIT 阶段，如果经过逃逸分析，发现一个对象不会被外界访问的话，那么经过 JIT 优化，就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换</p>
<p>举例如下：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String args[])</span> &#123;</span><br><span class="line">    alloc();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">alloc</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Point</span> <span class="variable">point</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Point</span>(<span class="number">1</span>,<span class="number">2</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;point.x&quot;</span> + point.x + <span class="string">&quot;;point.y&quot;</span> + point.y);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Point</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> x;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 以上代码，经过标量替换后如下</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">alloc</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">    System.out.println(<span class="string">&quot;point.x = &quot;</span> + x + <span class="string">&quot;; point.y=&quot;</span> + y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li><p>标量替换优点：</p>
<blockquote>
<p>Point 这个聚合量经过逃逸分析后，发现他并没有逃逸，就被替换成两个标量了，这样可以大大减少堆内存的占用。因为一旦不需要创建对象了，那么就不再需要分配堆内存了</p>
<p><strong>标量替换为栈上分配提供了很好的基础</strong></p>
</blockquote>
</li>
<li><p><strong>标量替换参数设置</strong></p>
<blockquote>
<p>参数<code>-XX:EliminateAllocations</code>：开启了标量替换（默认打开），允许将对象打散分配到栈上</p>
</blockquote>
</li>
</ul>
</li>
<li><p>设置参数罗列如下：</p>
<ul>
<li>参数<code>-server</code>：启动 Server 模式，因为在 server 模式下，才可以启用逃逸分析。</li>
<li>参数<code>-XX:+DoEscapeAnalysis</code>：启用逃逸分析</li>
<li>参数<code>-Xmx10m</code>：指定了堆空间最大为 10MB</li>
<li>参数<code>-XX:+PrintGC</code>：将打印 Gc 日志</li>
<li>参数<code>-XX:+EliminateAllocations</code>：开启了标量替换（默认打开），允许将对象打散分配在栈上，比如对象拥有 id 和 name 两个字段，那么这两个字段将会被视为两个独立的局部变量进行分配</li>
</ul>
</li>
</ul>
</li>
<li><p>逃逸分析总结：<strong>逃逸分析技术并不成熟</strong></p>
<blockquote>
<p>关于逃逸分析的论文在 1999 年就已经发表了，但直到 JDK1.6 才有实现，而且这项技术到如今也并不是十分成熟。</p>
<p>其根本原因就是<strong>无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的，这其实也是一个相对耗时的过程</strong></p>
<p>一个极端的例子，就是经过逃逸分析之后，发现没有一个对象是不逃逸的。那这个逃逸分析的过程就白白浪费掉了</p>
<p><strong>虽然这项技术并不十分成熟，但是它也是即时编译器优化技术中一个十分重要的手段</strong></p>
<p>注意到有一些观点，认为通过逃逸分析，JVM 会在栈上分配那些不会逃逸的对象，这在理论上是可行的，但是取决于 JVM 设计者的选择。据我所知，Oracle Hotspot JVM 中并未这么做，这一点在逃逸分析相关的文档里已经说明，所以<strong>可以明确所有的对象实例都是创建在堆上</strong></p>
<p>目前很多书籍还是基于 JDK7 以前的版本，JDK 已经发生了很大变化，intern 字符串缓存和静态变量曾经都被分配在永久代上，而永久代已经被元数据区取代。但是，intern 字符串缓存和静态变量并不是被转移到元数据区，而是直接在堆上分配，所以这一点同样符合前面一点的结论：<strong>对象实例都是分配在堆上</strong></p>
</blockquote>
</li>
</ol>
<h4 id="11-本章小结"><a href="#11-本章小结" class="headerlink" title="11.本章小结"></a>11.本章小结</h4><ol>
<li><p>年轻代是对象的诞生、成长、消亡的区域</p>
<blockquote>
<p>一个对象在这里产生、应用，最后被垃圾回收器收集、结束生命</p>
</blockquote>
</li>
<li><p>老年代放置长生命周期的对象</p>
<blockquote>
<p>通常都是从 survivor 区域筛选拷贝过来的 Java 对象</p>
<p>普通的对象会被分配在 TLAB 上</p>
<p>如果对象较大，JVM 会试图直接分配在 Eden 其他位置上</p>
<p>如果对象太大，完全无法在新生代找到足够长的连续空闲空间，JVM 就会直接分配到老年代</p>
<p>当 GC 只发生在年轻代中，回收年轻代对象的行为被称为 MinorGc</p>
<p>当 GC 发生在老年代时则被称为 MajorGc 或者 FullGC</p>
<p>一般的，MinorGc 的发生频率要比 MajorGC 高很多，即老年代中垃圾回收发生的频率将大大低于年轻代</p>
</blockquote>
</li>
</ol>
<h3 id="9-方法区"><a href="#9-方法区" class="headerlink" title="9.方法区"></a>9.方法区</h3><h4 id="1-栈、堆、方法区的交互关系"><a href="#1-栈、堆、方法区的交互关系" class="headerlink" title="1.栈、堆、方法区的交互关系"></a>1.栈、堆、方法区的交互关系</h4><ol>
<li><p>从内存结构位置划分</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%8D%E7%BD%AE.png" alt="方法区位置"></p>
</li>
<li><p>从是否线程共享角度划分</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E7%BB%93%E6%9E%84%E4%BD%8D%E7%BD%AE.png" alt="方法区结构位置"></p>
</li>
<li><p>三者关系在代码中的体现</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%A0%86%E3%80%81%E6%A0%88%E3%80%81%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BA%A4%E4%BA%92%E5%85%B3%E7%B3%BB.png" alt="堆、栈、方法区交互关系"></p>
</li>
</ol>
<h4 id="2-方法区的理解"><a href="#2-方法区的理解" class="headerlink" title="2.方法区的理解"></a>2.方法区的理解</h4><ol>
<li><p><a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4">官方文档</a></p>
</li>
<li><p>方法区在内存结构中的位置</p>
<blockquote>
<p>《Java 虚拟机规范》中明确说明：“尽管所有的方法区在逻辑上是属于堆的一部分，但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”</p>
<p><strong>但对于 HotSpot JVM 而言，方法区还有一个别名叫做 Non-Heap（非堆），目的就是要和堆分开</strong></p>
<p><strong>方法区看作是一块独立于 Java 堆的内存空间</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C%E5%A0%86%E5%88%86%E5%BC%80.png" alt="方法区和堆分开"></p>
</blockquote>
</li>
<li><p>方法区的基本理解</p>
<ul>
<li>方法区（Method Area）与 Java 堆一样，是各个线程共享的内存区域</li>
<li><strong>方法区在 JVM 启动的时候被创建，并且它的实际的物理内存空间中和 Java 堆区一样都可以是不连续的</strong></li>
<li>方法区的大小，跟堆空间一样，可以选择固定大小或者可扩展</li>
<li>方法区的大小决定了系统可以保存多少个类，如果系统定义了太多的类，导致方法区溢出，虚拟机同样会抛出内存溢出错误：<code>java.lang.OutOfMemoryError: PermGen space</code> 或者<code>java.lang.OutOfMemoryError: Metaspace</code>；常见造成溢出的情况：<ul>
<li>加载大量的第三方的 jar 包</li>
<li>Tomcat 部署的工程过多（30~50 个）</li>
<li>大量动态的生成反射类</li>
</ul>
</li>
<li><strong>关闭 JVM 就会释放这个区域的内存</strong></li>
</ul>
</li>
<li><p>HotSpot 中方法区的演进</p>
<ul>
<li><p>在 jdk7 及以前，习惯上把方法区，称为永久代；jdk8 开始，使用元空间取代了永久代</p>
</li>
<li><p>本质上方法区和永久代并不等价（仅是对 hotspot 而言的）。《Java 虚拟机规范》对如何实现方法区，不做统一要求。例如：BEA JRockit &#x2F; IBM J9 中不存在永久代的概念</p>
<blockquote>
<p>现在来看之前使用永久代（在 Java 虚拟机内存）不是好的 idea。导致 Java 程序更容易 OOM（超过<code>-XX:MaxPermsize</code>上限）</p>
<p>而到了 JDK8，终于完全废弃了永久代的概念，改用与 JRockit、J9 一样<strong>在本地内存中实现的元空间（Metaspace）</strong>来代替</p>
<p>元空间的本质和永久代类似，<strong>都是对 JVM 规范中方法区的实现</strong>。不过元空间与永久代最大的区别在于：<strong>元空间不在虚拟机设置的内存中，而是使用本地内存</strong></p>
<p>永久代、元空间二者并不只是名字变了，内部结构也调整了</p>
<p>根据《Java 虚拟机规范》的规定，如果方法区无法满足新的内存分配需求时，将抛出 OOM 异常</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%B0%B8%E4%B9%85%E4%BB%A3%E5%92%8C%E5%85%83%E7%A9%BA%E9%97%B4.png" alt="永久代和元空间"></p>
</li>
</ul>
</li>
</ol>
<h4 id="3-设置方法区大小与-OOM"><a href="#3-设置方法区大小与-OOM" class="headerlink" title="3.设置方法区大小与 OOM"></a>3.设置方法区大小与 OOM</h4><ol>
<li><p>方法区的大小不必是固定的，JVM 可以根据应用的需要动态调整</p>
<ul>
<li><p>jdk7 及以前</p>
<blockquote>
<p>通过 <code>-XX:Permsize</code> 来设置永久代初始分配空间。默认值是 20.75M</p>
<p>通过 <code>-XX:MaxPermsize</code> 来设定永久代最大可分配空间。32 位机器默认是 64M，64 位机器模式是 82M</p>
<p>当 JVM 加载的类信息容量超过了这个值，会报异常 <code>OutOfMemoryError:PermGen space</code></p>
</blockquote>
</li>
<li><p>JDK8 及以后</p>
<blockquote>
<p>元数据区大小可以使用参数 <code>-XX:MetaspaceSize</code> 和 <code>-XX:MaxMetaspaceSize</code> 指定</p>
<p>默认值依赖于平台。windows 下，<code>-XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1//即没有限制</code></p>
<p>与永久代不同，如果不指定大小，默认情况下，虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出，虚拟机一样会抛出异常 <code>OutOfMemoryError:Metaspace</code></p>
<p><code>-XX:MetaspaceSize</code>：设置初始的元空间大小。对于一个 64 位的服务器端 JVM 来说，其默认的<code>-XX:MetaspaceSize</code>值为 21MB。这就是初始的高水位线，一旦触及这个水位线，Full GC 将会被触发并卸载没用的类（即这些类对应的类加载器不再存活），然后这个高水位线将会重置。新的高水位线的值取决于 GC 后释放了多少元空间。如果释放的空间不足，那么在不超过<code>MaxMetaspaceSize</code>时，适当提高该值。如果释放空间过多，则适当降低该值</p>
<p>如果初始化的高水位线设置过低，上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到 Full GC 多次调用。为了避免频繁地 GC，建议将<code>-XX:MetaspaceSize</code>设置为一个相对较高的值</p>
</blockquote>
</li>
</ul>
</li>
<li><p>举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * jdk8中：</span></span><br><span class="line"><span class="comment"> * -XX:MetaspaceSize=10m-XX:MaxMetaspaceSize=10m</span></span><br><span class="line"><span class="comment"> * jdk6中：</span></span><br><span class="line"><span class="comment"> * -XX:PermSize=10m-XX:MaxPermSize=10m</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OOMTest</span> <span class="keyword">extends</span> <span class="title class_">ClassLoader</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>&#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="type">OOMTest</span> <span class="variable">test</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OOMTest</span>();</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>;i&lt;<span class="number">10000</span>;i++)&#123;</span><br><span class="line">                <span class="comment">//创建Classwriter对象，用于生成类的二进制字节码</span></span><br><span class="line">                <span class="type">ClassWriter</span> <span class="variable">classWriter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ClassWriter</span>(<span class="number">0</span>);</span><br><span class="line">                <span class="comment">//指明版本号，public，类名，包名，父类，接口</span></span><br><span class="line">                classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, <span class="string">&quot;Class&quot;</span> + i, nu1l, <span class="string">&quot;java/lang/Object&quot;</span>, <span class="literal">null</span>);</span><br><span class="line">                <span class="comment">//返回byte[]</span></span><br><span class="line">                <span class="type">byte</span>[] code = classWriter.toByteArray();</span><br><span class="line">                <span class="comment">//类的加载</span></span><br><span class="line">                test.defineClass(<span class="string">&quot;Class&quot;</span> + i, code, <span class="number">0</span>, code.length); <span class="comment">//CLass对象</span></span><br><span class="line">                j++;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span>&#123;</span><br><span class="line">            System.out.println(j);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>如何解决 OOM 问题</p>
<ul>
<li><p>要解决 OOM 异常或 heap space 的异常，一般的手段是首先通过内存映像分析工具（如 Eclipse Memory Analyzer）对 dump 出来的堆转储快照进行分析，重点是确认内存中的对象是否是必要的，也就是要先分清楚到底是出现了内存泄漏（Memory Leak）还是内存溢出（Memory Overflow）</p>
</li>
<li><p>如果是内存泄漏，可进一步通过工具查看泄漏对象到 GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与 GCRoots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息，以及 GCRoots 引用链的信息，就可以比较准确地定位出泄漏代码的位置</p>
</li>
<li><p>如果不存在内存泄漏，换句话说就是内存中的对象确实都还必须存活着，那就应当检查虚拟机的堆参数（<code>-Xmx</code>与<code>-Xms</code>），与机器物理内存对比看是否还可以调大，从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况，尝试减少程序运行期的内存消耗</p>
</li>
</ul>
</li>
</ol>
<h4 id="4-方法区内部结构"><a href="#4-方法区内部结构" class="headerlink" title="4.方法区内部结构"></a>4.方法区内部结构</h4><ol>
<li><p>方法区存储内容</p>
<blockquote>
<p>《深入理解 Java 虚拟机》书中对方法区（Method Area）存储内容描述如下：</p>
<p>它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E5%AD%98%E5%82%A8%E4%BF%A1%E6%81%AF.png" alt="方法区存储信息"></p>
</li>
<li><p>方法区内部结构</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E5%86%85%E9%83%A8%E7%BB%93%E6%9E%84.png" alt="方法区内部结构"></p>
<ul>
<li><p>类型信息</p>
<blockquote>
<p>对每个加载的类型（类 class、接口 interface、枚举 enum、注解 annotation），JVM 必须在方法区中存储以下类型信息：</p>
</blockquote>
<ul>
<li>这个类型的完整有效名称（全名 &#x3D; 包名.类名）</li>
<li>这个类型直接父类的完整有效名（对于 interface 或是 java.lang.object，都没有父类）</li>
<li>这个类型的修饰符（public，abstract，final 的某个子集）</li>
<li>这个类型直接接口的一个有序列表</li>
</ul>
</li>
<li><p>域（Field）信息</p>
<blockquote>
<p>JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序</p>
<p>域的相关信息包括：域名称、域类型、域修饰符（public，private，protected，static，final，volatile，transient 的某个子集）</p>
</blockquote>
</li>
<li><p>方法（Method）信息</p>
<blockquote>
<p>JVM 必须保存所有方法的以下信息，同域信息一样包括声明顺序</p>
</blockquote>
<ul>
<li><p>方法名称</p>
</li>
<li><p>方法的返回类型（或 void）</p>
</li>
<li><p>方法参数的数量和类型（按顺序）</p>
</li>
<li><p>方法的修饰符（public，private，protected，static，final，synchronized，native，abstract 的一个子集）</p>
</li>
<li><p>方法的字节码（bytecodes）、操作数栈、局部变量表及大小（abstract 和 native 方法除外）</p>
</li>
<li><p>异常表（abstract 和 native 方法除外）</p>
<blockquote>
<p>每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引</p>
</blockquote>
</li>
</ul>
</li>
<li><p>non-final 的类变量</p>
<ul>
<li>静态变量和类关联在一起，随着类的加载而加载，他们成为类数据在逻辑上的一部分</li>
<li>类变量被类的所有实例共享，即使没有类实例时，你也可以访问它</li>
</ul>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodAreaTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Order</span> <span class="variable">order</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Order</span>();</span><br><span class="line">        order.hello();</span><br><span class="line">        System.out.println(order.count);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Order</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">hello</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;hello!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>补充说明：类变量不同于全局常量（static final）</strong></p>
<p>被声明为 final 的类变量的处理方法则不同，每个全局常量在编译的时候就会被分配了</p>
</blockquote>
</li>
</ul>
</li>
<li><p>常量池</p>
<blockquote>
<p><strong>字节码文件内部包含了常量池</strong></p>
<p>一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外，还包含一项信息就是常量池表（Constant Pool Table），包括各种字面量和对类型、域和方法的符号引用</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%B8%B8%E9%87%8F%E6%B1%A0%E5%9C%A8%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE.png" alt="常量池在字节码中的位置"></p>
<ul>
<li><p>为什么需要常量池</p>
<blockquote>
<p>一个 java 源文件中的类、接口，编译后产生一个字节码文件。而 Java 中的字节码需要数据支持，通常这种数据会很大以至于不能直接存到字节码里，换另一种方式，可以存到常量池，这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池，之前有介绍</p>
<p>举例如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleClass</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sayHello</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>虽然生成 <code>.java</code> 文件只有 194 字节，但是里面却使用了 String、System、PrintStream 及 Object 等结构。这里的代码量其实很少了，如果代码多的话，引用的结构将会更多，这里就需要用到常量池了</p>
</blockquote>
</li>
<li><p>常量池存储内容</p>
<blockquote>
<p>常量池内存储的数据类型包括：</p>
</blockquote>
<ul>
<li>数量值</li>
<li>字符串值</li>
<li>类引用</li>
<li>字段引用</li>
<li>方法引用</li>
</ul>
<blockquote>
<p>举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodAreaTest2</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String args[])</span> &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>Object obj = new Object();</code>将会被翻译成如下字节码：</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="number">0</span>: <span class="keyword">new</span> #<span class="number">2</span>  <span class="comment">// Class java/lang/Object</span></span><br><span class="line"><span class="number">1</span>: dup</span><br><span class="line"><span class="number">2</span>: invokespecial <span class="comment">// Method java/lang/Object &quot;&lt;init&gt;&quot;() V</span></span><br></pre></td></tr></table></figure>
</blockquote>
</li>
<li><p>总结</p>
<blockquote>
<p>常量池、可以看做是一张表，虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型</p>
</blockquote>
</li>
</ul>
</li>
<li><p>运行时常量池</p>
<blockquote>
<p><a target="_blank" rel="noopener" href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html">官方文档</a></p>
<p><strong>方法区内部包含了运行时常量池</strong></p>
<p>要弄清楚方法区，需要理解清楚 ClassFile，因为加载类的信息都在方法区</p>
<p>要弄清楚方法区的运行时常量池，需要理解清楚 ClassFile 中的常量池</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM%E8%BF%90%E8%A1%8C%E6%97%B6%E7%9A%84%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84.png" alt="JVM运行时的内存结构"></p>
<ul>
<li>运行时常量池（Runtime Constant Pool）是方法区的一部分</li>
<li><strong>常量池表（Constant Pool Table）是 Class 文件的一部分，用于存放编译期生成的各种字面量与符号引用，这部分内容将在类加载后存放到方法区的运行时常量池中</strong></li>
<li><strong>运行时常量池，在加载类和接口到虚拟机后，就会创建对应的运行时常量池</strong></li>
<li>JVM 为每个已加载的类型（类或接口）都维护一个常量池。池中的数据项像数组项一样，是<strong>通过索引访问</strong>的</li>
<li>运行时常量池中包含多种不同的常量，包括编译期就已经明确的数值字面量，也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了，这里换为真实地址</li>
<li><strong>运行时常量池，相对于 Class 文件常量池的另一重要特征是：具备动态性</strong></li>
<li>运行时常量池类似于传统编程语言中的符号表（symboltable），但是它所包含的数据却比符号表要更加丰富一些</li>
<li>当创建类或接口的运行时常量池时，如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值，则 JVM 会抛 OutOfMemoryError 异常</li>
</ul>
</li>
</ol>
<h4 id="5-方法区使用举例"><a href="#5-方法区使用举例" class="headerlink" title="5.方法区使用举例"></a>5.方法区使用举例</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodAreaDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String args[])</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">500</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> x / y;</span><br><span class="line">        <span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">50</span>;</span><br><span class="line">        System.out.println(a + b);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B1.png" alt="方法区使用举例1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B2.png" alt="方法区使用举例2"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B3.png" alt="方法区使用举例3"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B4.png" alt="方法区使用举例4"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B5.png" alt="方法区使用举例5"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B6.png" alt="方法区使用举例6"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B7.png" alt="方法区使用举例7"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B8.png" alt="方法区使用举例8"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B9.png" alt="方法区使用举例9"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B10.png" alt="方法区使用举例10"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B11.png" alt="方法区使用举例11"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B12.png" alt="方法区使用举例12"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B13.png" alt="方法区使用举例13"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B14.png" alt="方法区使用举例14"></p>
<h4 id="6-方法区演进细节"><a href="#6-方法区演进细节" class="headerlink" title="6.方法区演进细节"></a>6.方法区演进细节</h4><ol>
<li><p>方法区演进</p>
<blockquote>
<p>首先明确：只有 Hotspot 才有永久代。BEA JRockit、IBMJ9 等来说，是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节，不受《Java 虚拟机规范》管束，并不要求统一</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">JDK1.6 及之前</th>
<th align="left">有永久代（permanet），静态变量存储在永久代上</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>JDK1.7</strong></td>
<td align="left"><strong>有永久代，但已经逐步 “去永久代”，字符串常量池，静态变量移到堆中</strong></td>
</tr>
<tr>
<td align="left"><strong>JDK1.8</strong></td>
<td align="left"><strong>无永久代，类型信息，字段，方法，常量保存在本地内存的元空间，但字符串常量池、静态变量仍然在堆中</strong></td>
</tr>
</tbody></table>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK6%E6%BC%94%E8%BF%9B.png" alt="JDK6演进"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK7%E6%BC%94%E8%BF%9B.png" alt="JDK7演进"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JDK8%E6%BC%94%E8%BF%9B.png" alt="JDK8演进"></p>
</li>
<li><p>为什么用元空间代替永久代</p>
<ul>
<li><p><a target="_blank" rel="noopener" href="http://openjdk.java.net/jeps/122">官方说明</a></p>
<blockquote>
<p>JRockit 是和 HotSpot 融合后的结果，因为 JRockit 没有永久代，所以他们不需要配置永久代</p>
<p>随着 Java8 的到来，HotSpot VM 中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个<strong>与堆不相连的本地内存区域，这个区域叫做元空间（Metaspace）</strong></p>
<p><strong>由于类的元数据分配在本地内存中，元空间的最大可分配空间就是系统可用内存空间</strong></p>
</blockquote>
</li>
<li><p>改动的必要性：</p>
<ul>
<li><p><strong>为永久代设置空间大小是很难确定的</strong></p>
<blockquote>
<p>在某些场景下，如果动态加载类过多，容易产生 Perm 区的 oom。比如某个实际 Web 工 程中，因为功能点比较多，在运行过程中，要不断动态加载很多类，经常出现致命错误</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;Exception in thread &#x27;dubbo client x.x connector&#x27; java.lang.OutOfMemoryError:PermGen space&quot;</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>而元空间和永久代之间最大的区别在于：元空间并不在虚拟机中，而是使用本地内存。 因此，默认情况下，元空间的大小仅受本地内存限制</strong></p>
</blockquote>
</li>
<li><p><strong>对永久代进行调优是很困难的</strong></p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>有些人认为方法区（如 HotSpot 虚拟机中的元空间或者永久代）是没有垃圾收集行为的，其实不然</p>
<p>《Java 虚拟机规范》对方法区的约束是非常宽松的，提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在（如 JDK 11 时期的 ZGC 收集器就不支持类卸载）</p>
<p> 一般来说这个区域的回收效果比较难令人满意，尤其是类型的卸载，条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前 Sun 公司的 Bug 列表中，曾出现过的若干个严重的 Bug 就是由于低版本的 HotSpot 虚拟机对此区域未完全回收而导致内存泄漏</p>
<p><strong>方法区的垃圾收集主要回收两部分内容：常量池中废弃的常量和不再使用的类型</strong></p>
</blockquote>
</li>
<li><p>StringTable 为什么要调整位置</p>
<ul>
<li>jdk7 中将 StringTable 放到了堆空间中。因为永久代的回收效率很低，在 full gc 的时候才会触发</li>
<li>full gc 是老年代的空间不足、永久代不足时才会触发，这就导致 StringTable 回收效率不高。而开发中会有大量的字符串被创建，回收效率低，导致永久代内存不足。<strong>放到堆里，能及时回收内存</strong></li>
</ul>
</li>
<li><p>静态变量存放在哪里</p>
<blockquote>
<p> <strong>静态引用对应的对象实体</strong>始终都存在堆空间</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 静态引用对应的对象实体始终都存在堆空间</span></span><br><span class="line"><span class="comment"> * jdk7:</span></span><br><span class="line"><span class="comment"> * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails</span></span><br><span class="line"><span class="comment"> * jdk8:</span></span><br><span class="line"><span class="comment"> * -Xms200m -Xmx200m-XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StaticFieldTest</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">byte</span>[] arr = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span> * <span class="number">1024</span> * <span class="number">100</span>];</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        System.out.println(StaticFieldTest.arr);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>静态变量的引用</strong>随着 JDK 版本变化</p>
<ul>
<li><p><code>staticobj</code> 随着 Test 的类型信息存放在方法区</p>
</li>
<li><p><code>instanceobj</code> 随着 Test 的对象实例存放在 Java 堆</p>
</li>
<li><p><code>localobject</code> 则是存放在 foo() 方法栈帧的局部变量表中</p>
</li>
<li><p><strong>三者各自指向的实例对象都在堆区</strong></p>
</li>
</ul>
<p>测试发现：三个对象的数据在内存中的地址都落在 Eden 区范围内</p>
<p>所以结论：只要是对象实例必然会在 Java 堆中分配</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/** * staticobj、instanceobj、Localobj存放在哪里？ */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StaticobjTest</span> &#123;    </span><br><span class="line">	<span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">        <span class="comment">// 存放在方法区</span></span><br><span class="line">		<span class="keyword">static</span> <span class="type">ObjectHolder</span> <span class="variable">staticobj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectHolder</span>();    </span><br><span class="line">        <span class="comment">// 存放在堆</span></span><br><span class="line">		<span class="type">ObjectHolder</span> <span class="variable">instanceobj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectHolder</span>();    </span><br><span class="line">        </span><br><span class="line">		<span class="keyword">void</span> <span class="title function_">foo</span><span class="params">()</span>&#123;</span><br><span class="line">            <span class="comment">// 存放在局部变量表</span></span><br><span class="line">			<span class="type">ObjectHolder</span> <span class="variable">localobj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectHolder</span>();            </span><br><span class="line">			System.out.println(<span class="string">&quot;done&quot;</span>);        </span><br><span class="line">		&#125;        </span><br><span class="line">	&#125;    </span><br><span class="line">	</span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ObjectHolder</span>&#123;&#125;  </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;            </span><br><span class="line">        <span class="type">Test</span> <span class="variable">test</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StaticobjTest</span>.Test();            </span><br><span class="line">        test.foo();        </span><br><span class="line">    &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="7-方法区的垃圾回收"><a href="#7-方法区的垃圾回收" class="headerlink" title="7.方法区的垃圾回收"></a>7.方法区的垃圾回收</h4><blockquote>
<p>有些人认为方法区（如 Hotspot 虚拟机中的元空间或者永久代）是没有垃圾收集行为的，其实不然。《Java 虚拟机规范》对方法区的约束是非常宽松的，提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在（如 JDK11 时期的 zGC 收集器就不支持类卸载）</p>
</blockquote>
<ol>
<li><p>方法区常量池存储内容</p>
<ul>
<li><p>常量（字面量）</p>
<blockquote>
<p>字面量比较接近 Java 语言层次的常量概念，如文本字符串、被声明为 final 的常量值等</p>
</blockquote>
</li>
<li><p>符号引用</p>
<blockquote>
<p>符号引用则属于编译原理方面的概念，包括下面三类常量：</p>
</blockquote>
<ul>
<li>类和接口的全限定名</li>
<li>字段的名称和描述符</li>
<li>方法的名称和描述符</li>
</ul>
</li>
</ul>
</li>
<li><p>回收条件</p>
<ul>
<li><p>字面量</p>
<blockquote>
<p>HotSpot 虚拟机对常量池的回收策略是很明确的，<strong>只要常量池中的常量没有被任何地方引用，就可以被回收</strong></p>
</blockquote>
</li>
<li><p>符号引用（需同时满足以下三个条件，较为苛刻）</p>
<ul>
<li><strong>该类所有的实例都已经被回收</strong>，也就是 Java 堆中不存在该类及其任何派生子类的实例</li>
<li><strong>加载该类的类加载器已经被回收</strong>，这个条件除非是经过精心设计的可替换类加载器的场景，如 OSGi、JSP 的重加载等，否则通常是很难达成的</li>
<li><strong>该类对应的 java.lang.Class 对象没有在任何地方被引用</strong>，无法在任何地方通过反射访问该类的方法</li>
</ul>
<blockquote>
<p>Java 虚拟机被允许对满足上述三个条件的无用类进行回收，这里说的仅仅是“被允许”，而并不是和对象一样，没有引用了就必然会回收。关于是否要对类型进行回收，HotSpot 虚拟机提供了<code>-Xnoclassgc</code>参数进行控制，还可以使用<code>-verbose:class</code> 以及 <code>-XX:+TraceClassLoading</code>、<code>-XX:+TraceClassUnLoading</code>查看类加载和卸载信息</p>
<p>在大量使用反射、动态代理、CGLib 等字节码框架，动态生成 JSP 以及 OSGi 这类频繁自定义类加载器的场景中，<strong>通常都需要 Java 虚拟机具备类型卸载的能力，以保证不会对方法区造成过大的内存压力</strong></p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="8-总结与面试题"><a href="#8-总结与面试题" class="headerlink" title="8.总结与面试题"></a>8.总结与面试题</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E5%8C%BA%E6%80%BB%E7%BB%93.png" alt="方法区总结"></p>
<blockquote>
<p><strong>百度</strong></p>
<ul>
<li>说一下 JVM 内存模型吧，有哪些区？分别干什么的？</li>
</ul>
<p><strong>蚂蚁金服</strong></p>
<ul>
<li><p>Java8 的内存分代改进 JVM 内存分哪几个区，每个区的作用是什么？</p>
</li>
<li><p>一面：JVM 内存分布&#x2F;内存结构？栈和堆的区别？堆的结构？为什么两个 survivor 区？</p>
</li>
<li><p>二面：Eden 和 survior 的比例分配</p>
</li>
</ul>
<p><strong>小米</strong></p>
<ul>
<li>jvm 内存分区，为什么要有新生代和老年代</li>
</ul>
<p><strong>字节跳动</strong></p>
<ul>
<li><p>二面：Java 的内存分区</p>
</li>
<li><p>二面：讲讲 vm 运行时数据库区 什么时候对象会进入老年代？</p>
</li>
</ul>
<p><strong>京东</strong></p>
<ul>
<li><p>JVM 的内存结构，Eden 和 Survivor 比例。</p>
</li>
<li><p>JVM 内存为什么要分成新生代，老年代，持久代。</p>
</li>
<li><p>新生代中为什么要分为 Eden 和 survivor。</p>
</li>
</ul>
<p><strong>天猫</strong></p>
<ul>
<li><p>一面：Jvm 内存模型以及分区，需要详细到每个区放什么。</p>
</li>
<li><p>一面：JVM 的内存模型，Java8 做了什么改</p>
</li>
</ul>
<p><strong>拼多多</strong></p>
<ul>
<li>JVM 内存分哪几个区，每个区的作用是什么？</li>
</ul>
<p><strong>美团</strong></p>
<ul>
<li><p>java 内存分配 jvm 的永久代中会发生垃圾回收吗？</p>
</li>
<li><p>一面：jvm 内存分区，为什么要有新生代和老年代？</p>
</li>
</ul>
</blockquote>
<h3 id="10-对象实例化与访问定位"><a href="#10-对象实例化与访问定位" class="headerlink" title="10.对象实例化与访问定位"></a>10.对象实例化与访问定位</h3><h4 id="1-对象的实例化"><a href="#1-对象的实例化" class="headerlink" title="1.对象的实例化"></a>1.对象的实例化</h4><ol>
<li><p>创建对象的方式</p>
<ul>
<li>new：最常见的方式、Xxx 的静态方法，XxxBuilder&#x2F;XxxFactory 的静态方法</li>
<li>Class 的 newInstance 方法：反射的方式，只能调用空参的构造器，权限必须是 public</li>
<li>Constructor 的 newInstance(XXX)：反射的方式，可以调用空参、带参的构造器，权限没有要求</li>
<li>使用 clone()：不调用任何的构造器，要求当前的类需要实现 Cloneable 接口，实现 clone()</li>
<li>使用序列化：从文件中、从网络中获取一个对象的二进制流</li>
<li>第三方库 Objenesis</li>
</ul>
</li>
<li><p>创建对象的步骤</p>
<blockquote>
<p>前面所述是从字节码角度看待对象的创建过程，现在从执行步骤的角度来分析：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%AD%A5%E9%AA%A4.png" alt="创建对象的步骤"></p>
</blockquote>
<ul>
<li><p>判断对象对应的类是否加载、链接、初始化</p>
<blockquote>
<p>虚拟机遇到一条 new 指令，首先去检查这个指令的参数能否在 Metaspace 的常量池中定位到一个类的符号引用，并且检查这个符号引用代表的类是否已经被加载，解析和初始化（即判断类元信息是否存在）</p>
<p>如果没有，那么在双亲委派模式下，使用当前类加载器以 <code>ClassLoader + 包名 + 类名</code> 为 key 进行查找对应的 <code>.class</code> 文件</p>
<ul>
<li>如果没有找到文件，则抛出 <code>ClassNotFoundException</code> 异常</li>
<li>如果找到文件，则进行类加载，并生成对应的 Class 对象</li>
</ul>
</blockquote>
</li>
<li><p>为对象分配内存</p>
<blockquote>
<p>首先计算对象占用空间的大小，接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量，仅分配引用变量空间即可，即 4 个字节大小</p>
<p>选择哪种分配方式由 Java 堆是否规整所决定，而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定</p>
</blockquote>
<ul>
<li><p><strong>如果内存规整</strong>：虚拟机将采用的是<strong>指针碰撞法（Bump The Point）</strong>来为对象分配内存</p>
<blockquote>
<p>意思是所有用过的内存在一边，空闲的内存放另外一边，中间放着一个指针作为分界点的指示器，分配内存就仅仅是把指针指向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是 Serial ，ParNew 这种基于压缩算法的，虚拟机采用这种分配方式。一般使用带 Compact（整理）过程的收集器时，使用指针碰撞</p>
</blockquote>
</li>
<li><p><strong>如果内存不规整</strong>：虚拟机需要维护一个<strong>空闲列表（Free List）</strong>来为对象分配内存</p>
<blockquote>
<p>已使用的内存和未使用的内存相互交错，那么虚拟机将采用的是空闲列表来为对象分配内存。意思是虚拟机维护了一个列表，记录上那些内存块是可用的，再分配的时候从列表中找到一块足够大的空间划分给对象实例，并更新列表上的内容</p>
</blockquote>
</li>
</ul>
</li>
<li><p>处理并发安全问题</p>
</li>
<li><p>采用 CAS 失败重试、区域加锁保证更新的原子性</p>
</li>
<li><p>每个线程预先分配一块 TLAB：通过设置 <code>-XX:+UseTLAB</code> 参数来设定</p>
</li>
<li><p>初始化分配到的空间</p>
<ul>
<li>所有属性设置默认值，保证对象实例字段在不赋值时可以直接使用</li>
</ul>
</li>
<li><p>设置对象的对象头</p>
<ul>
<li>将对象的所属类（即类的元数据信息）、对象的 HashCode 和对象的 GC 信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于 JVM 实现</li>
</ul>
</li>
<li><p>执行 <code>init()</code> 方法进行初始化</p>
<blockquote>
<p>在 Java 程序的视角看来，初始化才正式开始</p>
<p><strong>初始化成员变量，执行实例化代码块，调用类的构造方法</strong>，并把堆内对象的首地址赋值给引用变量</p>
<p><strong>因此一般来说（由字节码中跟随 invokespecial 指令所决定），new 指令之后会接着就是执行方法，把对象按照程序员的意愿进行初始化（执行构造函数），这样一个真正可用的对象才算完成创建出来</strong></p>
</blockquote>
<ul>
<li><p><strong>给对象属性赋值的操作</strong></p>
<ul>
<li>属性的默认初始化</li>
<li>显式初始化</li>
<li>代码块中初始化</li>
<li>构造器中初始化</li>
</ul>
</li>
<li><p><strong>对象实例化的过程</strong></p>
<ol>
<li><p>加载类元信息</p>
</li>
<li><p>为对象分配内存</p>
</li>
<li><p>处理并发问题</p>
</li>
<li><p>属性的默认初始化（零值初始化）</p>
</li>
<li><p>设置对象头信息</p>
</li>
<li><p>属性的显示初始化、代码块中初始化、构造器中初始化</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="2-对象的内存布局"><a href="#2-对象的内存布局" class="headerlink" title="2.对象的内存布局"></a>2.对象的内存布局</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80.png" alt="对象的内存布局"></p>
<ol>
<li><p>对象头</p>
<blockquote>
<p>对象头包含了两部分，分别是<strong>运行时元数据（Mark Word）</strong>和<strong>类型指针</strong></p>
<p>如果是数组，还需要记录数组的长度</p>
</blockquote>
<ul>
<li>运行时元数据<ul>
<li>哈希值（HashCode）</li>
<li>GC 分代年龄</li>
<li>锁状态标志</li>
<li>线程持有的锁</li>
<li>偏向线程 ID</li>
<li>翩向时间戳</li>
</ul>
</li>
<li>类型指针（并不是所有对象都会保存类型指针）<ul>
<li>指向类元数据 InstanceKlass，确定该对象所属的类型</li>
</ul>
</li>
</ul>
</li>
<li><p>实例数据</p>
<blockquote>
<p>它是对象真正存储的有效信息，包括程序代码中定义的各种类型的字段（包括从父类继承下来的和本身拥有的字段）</p>
</blockquote>
<ul>
<li>相同宽度的字段总是被分配在一起</li>
<li><strong>父类中定义的变量会出现在子类之前</strong></li>
<li>如果 <code>CompactFields 参数为 true</code>（默认为 true），则子类的窄变量可能插入到父类变量的空隙</li>
</ul>
</li>
<li><p>对齐填充</p>
<blockquote>
<p>不是必须的，也没有特别的含义，仅仅起到占位符的作用</p>
<p>举例</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Account</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Customer</span>&#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">id</span> <span class="operator">=</span> <span class="number">1001</span>;</span><br><span class="line">    String name;</span><br><span class="line">    Account acct;</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">        name = <span class="string">&quot;匿名客户&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Customer</span><span class="params">()</span> &#123;</span><br><span class="line">        acct = <span class="keyword">new</span> <span class="title class_">Account</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CustomerTest</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(string[] args)</span>&#123;</span><br><span class="line">        Customer cust=<span class="keyword">new</span> <span class="title class_">Customer</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码图示如下</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E5%B8%83%E5%B1%80%E4%B8%BE%E4%BE%8B.png" alt="对象布局举例"></p>
</blockquote>
</li>
</ol>
<h4 id="3-对象的访问定位"><a href="#3-对象的访问定位" class="headerlink" title="3.对象的访问定位"></a>3.对象的访问定位</h4><p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D.png" alt="对象的访问定位"></p>
<blockquote>
<p>JVM 是如何通过栈帧中的对象引用访问到其内部的对象实例呢？</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%E5%88%B0%E5%AE%9E%E4%BE%8B.png" alt="对象引用到实例"></p>
</blockquote>
<ol>
<li><p>句柄访问</p>
<blockquote>
<p>reference 中存储稳定句柄地址，对象被移动（垃圾收集时移动对象很普遍）时只会改变句柄中实例数据指针即可，reference 本身不需要被修改</p>
<p>缺点：需额外空间作为句柄池；多次指向，访问效率低</p>
<p>优点：句柄池空间稳定，对象实例位置修改时只需修改句柄池内容，无需修改 reference</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8F%A5%E6%9F%84%E8%AE%BF%E9%97%AE.png" alt="句柄访问"></p>
</li>
<li><p>直接指针（HotSpot 采用）</p>
<blockquote>
<p>直接指针是局部变量表中的引用，直接指向堆中的实例，在对象实例中有类型指针，指向的是方法区中的对象类型数据</p>
<p><strong>优缺点反</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%9B%B4%E6%8E%A5%E6%8C%87%E9%92%88.png" alt="直接指针"></p>
</li>
</ol>
<h3 id="11-直接内存"><a href="#11-直接内存" class="headerlink" title="11.直接内存"></a>11.直接内存</h3><h4 id="1-直接内存概述"><a href="#1-直接内存概述" class="headerlink" title="1.直接内存概述"></a>1.直接内存概述</h4><blockquote>
<p><strong>不是虚拟机运行时数据区的一部分，也不是《Java 虚拟机规范》中定义的内存区域</strong></p>
<p><strong>直接内存是在 Java 堆外的</strong>、直接向系统申请的内存区间</p>
<p>来源于 NIO，通过存在堆中的 DirectByteBuffer 操作 Native 内存。通常<strong>访问直接内存的速度会优于 Java 堆，即读写性能高</strong></p>
<p>NIO（New IO &#x2F; Non-Blocking IO）</p>
</blockquote>
<table>
<thead>
<tr>
<th align="center">IO</th>
<th align="center">NIO</th>
</tr>
</thead>
<tbody><tr>
<td align="center">byte[] &#x2F; char[]</td>
<td align="center">Buffer</td>
</tr>
<tr>
<td align="center">Stream</td>
<td align="center">Channel</td>
</tr>
</tbody></table>
<ol>
<li><p>因此出于性能考虑，读写频繁的场合可能会考虑使用直接内存</p>
</li>
<li><p>Java 的 NIO 库允许 Java 程序使用直接内存，用于数据缓冲区</p>
</li>
</ol>
<h4 id="2-非直接缓冲区"><a href="#2-非直接缓冲区" class="headerlink" title="2.非直接缓冲区"></a>2.非直接缓冲区</h4><blockquote>
<p>使用 IO 读写文件，需要与磁盘交互，需要由用户态切换到内核态</p>
<p>在内核态时，需要两份内存存储重复数据，效率低</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E9%9D%9E%E7%9B%B4%E6%8E%A5%E7%BC%93%E5%86%B2%E5%8C%BA.png" alt="非直接缓冲区"></p>
<h4 id="3-直接缓冲区-直接内存"><a href="#3-直接缓冲区-直接内存" class="headerlink" title="3.直接缓冲区(直接内存)"></a>3.直接缓冲区(直接内存)</h4><blockquote>
<p>使用 NIO 时，操作系统划出的直接缓存区可以被 java 代码直接访问，只有一份。NIO 适合对大文件的读写操作</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%9B%B4%E6%8E%A5%E7%BC%93%E5%86%B2%E5%8C%BA.png" alt="直接缓冲区"></p>
<blockquote>
<p>直接内存也可能产生 OutOfMemoryError 异常</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Exception in thread <span class="string">&quot;main&quot;</span> java.lang.OutOfMemoryError: Direct buffer memory</span><br><span class="line">    at java.nio.Bits.reserveMemory(Bits.java:<span class="number">693</span>)</span><br><span class="line">    at java.nio.DirectByteBuffer.&lt;init&gt;(DirectByteBuffer.java:<span class="number">123</span>)</span><br><span class="line">    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:<span class="number">311</span>)</span><br><span class="line">    at com.atguigu.java.BufferTest2.main(BufferTest2.java:<span class="number">20</span>)</span><br></pre></td></tr></table></figure>

<p>由于直接内存在 Java 堆外，因此它的大小不会直接受限于 <code>-Xmx</code> 指定的最大堆大小，但是系统内存是有限的，Java 堆和直接内存的总和依然受限于操作系统能给出的最大内存</p>
<ul>
<li>分配回收成本较高</li>
<li>不受 JVM 内存回收管理</li>
</ul>
<p>直接内存大小可以通过<code>MaxDirectMemorySize</code>设置。如果不指定，默认与堆的最大值 <code>-Xmx</code> 参数值一致</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98.png" alt="直接内存"></p>
</blockquote>
<h3 id="12-执行引擎"><a href="#12-执行引擎" class="headerlink" title="12.执行引擎"></a>12.执行引擎</h3><h4 id="1-执行引擎概述"><a href="#1-执行引擎概述" class="headerlink" title="1.执行引擎概述"></a>1.执行引擎概述</h4><ol>
<li><p>基本内容</p>
<blockquote>
<p>执行引擎是 Java 虚拟机核心的组成部分之一</p>
<p>执行引擎属于 JVM 的下层，里面包括<strong>解释器、及时编译器、垃圾回收器</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E7%BB%93%E6%9E%84.png" alt="执行引擎结构"></p>
</blockquote>
<ul>
<li><code>虚拟机</code>是一个相对于<code>物理机</code>的概念，这两种机器都有代码执行能力，其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的，而<strong>虚拟机的执行引擎则是由软件自行实现的</strong>，因此可以不受物理条件制约地定制指令集与执行引擎的结构体系，<strong>能够执行那些不被硬件直接支持的指令集格式</strong></li>
<li><strong>JVM 的主要任务是负责装载字节码到其内部</strong>，但字节码并不能够直接运行在操作系统之上，因为字节码指令并非等价于本地机器指令，它内部包含的仅仅只是一些能够被 JVM 所识别的字节码指令、符号表，以及其他辅助信息</li>
<li>如果想要让一个 Java 程序运行起来，<strong>执行引擎（Execution Engine）的任务就是将字节码指令解释&#x2F;编译为对应平台上的本地机器指令</strong>。简单来说，<strong>JVM 中的执行引擎充当了将高级语言翻译为机器语言的译者</strong></li>
</ul>
</li>
<li><p>工作流程</p>
<ul>
<li>执行引擎在执行的过程中究竟需要执行什么样的<strong>字节码指令完全依赖于 PC 寄存器</strong></li>
<li>每当执行完一项指令操作后，PC 寄存器就会更新下一条需要被执行的指令地址</li>
<li>当然方法在执行的过程中，执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在 Java 堆区中的对象实例信息，以及通过对象头中的元数据指针定位到目标对象的类型信息</li>
</ul>
<blockquote>
<p><strong>从外观上来看，所有的 Java 虚拟机的执行引擎输入，输出都是一致的：输入的是字节码二进制流，处理过程是字节码解析执行的等效过程，输出的是执行过程</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png" alt="执行引擎工作流程"></p>
</li>
</ol>
<h4 id="2-Java-代码编译和执行过程"><a href="#2-Java-代码编译和执行过程" class="headerlink" title="2.Java 代码编译和执行过程"></a>2.Java 代码编译和执行过程</h4><ol>
<li><p>代码编译和执行过程</p>
<blockquote>
<p>大部分的程序代码转换成物理机的目标代码或虚拟机能执行的指令集之前，都需要经过下图中的各个步骤（抽象语法树后分支，蓝绿路径选一条）</p>
<ul>
<li>绿色：（解释过程）边解释边执行，如 python</li>
<li>蓝色：（编译过程）编译完成后执行，如 C、C++</li>
<li><strong>Java 半编译半解释</strong></li>
</ul>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%BC%96%E8%AF%91%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.png" alt="编译执行过程"></p>
<blockquote>
<p><strong>Java 代码的编译</strong>是由 <strong>Java 源码编译器（前端编译器）</strong>来完成，流程图如下所示</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%89%8D%E7%AB%AF%E7%BC%96%E8%AF%91%E5%99%A8.png" alt="前端编译器"></p>
<blockquote>
<p><strong>Java 字节码的执行</strong>是由 <strong>JVM 执行引擎（后端编译器）</strong>来完成，流程图如下所示</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%90%8E%E7%AB%AF%E7%BC%96%E8%AF%91%E5%99%A8.png" alt="后端编译器"></p>
</li>
<li><p>解释器和 JIT 编译器概述</p>
<ul>
<li><p>解释器</p>
<blockquote>
<p>当 Java 虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行，将每条字节码文件中的内容<code>翻译</code>为对应平台的本地机器指令执行</p>
</blockquote>
</li>
<li><p>JIT 编译器</p>
<blockquote>
<p>JIT（Just In Time Compiler）编译器：就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言</p>
</blockquote>
</li>
</ul>
</li>
<li><p>为什么 Java 是半编译半解释型语言</p>
<blockquote>
<p>JDK1.0 时代，将 Java 语言定位为<code>解释执行</code>还是比较准确的</p>
<p>再后来，Java 也发展出可以直接生成本地代码的编译器</p>
<p>现在 JVM 在执行 Java 代码的时候，<strong>通常都会将解释执行与编译执行二者结合起来进行</strong></p>
<p><strong>半编译半解释 !&#x3D; 先编译后解释</strong></p>
<p><strong>半编译半解释 &#x3D;&#x3D; 既可以编译运行，也可以解释运行</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%8D%8A%E8%A7%A3%E9%87%8A%E5%8D%8A%E7%BC%96%E8%AF%91.png" alt="半解释半编译"></p>
</li>
</ol>
<h4 id="3-机器码、指令、汇编语言"><a href="#3-机器码、指令、汇编语言" class="headerlink" title="3.机器码、指令、汇编语言"></a>3.机器码、指令、汇编语言</h4><ol>
<li><p>机器码</p>
<blockquote>
<p>各种用二进制编码方式表示的指令，叫做机器指令码。刚开始人们就用它采编写程序，这就是机器语言</p>
<p>机器语言虽然能够被计算机理解和接受，但和人们的语言差别太大，不易被人们理解和记忆，并且用它编程容易出差错</p>
<p>用它编写的程序一经输入计算机，CPU 直接读取运行，因此和其他语言编的程序相比，执行速度最快</p>
<p>机器指令与 CPU 紧密相关，所以不同种类的 CPU 所对应的机器指令也就不同</p>
</blockquote>
</li>
<li><p>指令</p>
<blockquote>
<p>由于机器码是有 0 和 1 组成的二进制序列，可读性实在太差，于是人们发明了指令</p>
<p>指令就是把机器码中特定的 0 和 1 序列，简化成对应的指令（一般为英文简写，如 mov，inc 等），可读性稍好</p>
<p>由于不同的硬件平台，执行同一个操作，对应的机器码可能不同，所以不同的硬件平台的同一种指令（比如 mov），对应的机器码也可能不同</p>
</blockquote>
</li>
<li><p>指令集</p>
<blockquote>
<p>不同的硬件平台，各自支持的指令是有差别的。因此每个平台所支持的指令，称之为对应平台的指令集。 如常见的</p>
<ul>
<li>x86 指令集，对应的是 x86 架构的平台</li>
<li>ARM 指令集，对应的是 ARM 架构的平台</li>
</ul>
</blockquote>
</li>
<li><p>汇编语言</p>
<blockquote>
<p>由于指令的可读性还是太差，于是人们又发明了汇编语言</p>
<p>在汇编语言中，<strong>用助记符（Mnemonics）代替机器指令的操作码，用地址符号（Symbol）或标号（Label）代替指令或操作数的地址</strong>。在不同的硬件平台，汇编语言对应着不同的机器语言指令集，通过汇编过程转换成机器指令</p>
<p>由于计算机只认识指令码，所以用<strong>汇编语言编写的程序还必须翻译成机器指令码</strong>，计算机才能识别和执行</p>
</blockquote>
</li>
<li><p>高级语言</p>
<blockquote>
<p>为了使计算机用户编程序更容易些，后来就出现了各种高级计算机语言。高级语言比机器语言、汇编语言更接近人的语言</p>
<p>当计算机执行高级语言编写的程序时，<strong>仍然需要把程序解释和编译成机器的指令码</strong>。完成这个过程的程序就叫做<strong>解释程序</strong>或<strong>编译程序</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%AF%AD%E8%A8%80%E5%B1%82%E7%BA%A7.png" alt="语言层级"></p>
<blockquote>
<p>高级语言也不是直接翻译成机器指令，而是翻译成汇编语言码，如下面说的 C 和 C++</p>
<p><strong>编译过程又可以分成两个阶段：编译和汇编</strong></p>
<p><strong>编译过程</strong>：是读取源程序（字符流），对之进行词法和语法的分析，将高级语言指令转换为功能等效的汇编代码<strong>（高级语言 -&gt; 汇编语言）</strong></p>
<p><strong>汇编过程</strong>：实际上指把汇编语言代码翻译成目标机器指令的过程<strong>（汇编语言 -&gt; 机器语言）</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E7%BC%96%E8%AF%91%E6%B1%87%E7%BC%96.png" alt="编译汇编"></p>
</li>
<li><p>字节码</p>
<blockquote>
<p>字节码是一种中间状态（中间码）的二进制代码（文件），它比机器码更抽象，需要直译器转译后才能成为机器码</p>
<p>字节码主要为了实现特定软件运行和软件环境、<strong>与硬件环境无关</strong></p>
<p>字节码的实现方式是通过编译器和虚拟机器。<strong>编译器将源码编译成字节码，特定平台上的虚拟机器将字节码转译为可以直接执行的指令</strong></p>
<p>字节码典型的应用为：Java bytecode</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%AD%97%E8%8A%82%E7%A0%81.png" alt="字节码"></p>
</li>
</ol>
<h4 id="4-解释器"><a href="#4-解释器" class="headerlink" title="4.解释器"></a>4.解释器</h4><ol>
<li><p>解释器工作机制</p>
<blockquote>
<p><strong>JVM 设计者们的初衷仅仅只是单纯地为了满足 Java 程序实现跨平台特性</strong>，因此避免采用静态编译的方式直接生成本地机器指令，从而诞生了实现解释器在运行时采用逐行解释字节码执行程序的想法</p>
<p>为什么 Java 源文件不直接翻译成 JMV，而是翻译成字节码文件？可能是因为直接翻译的代价是比较大的</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM%E8%B7%A8%E5%B9%B3%E5%8F%B0.png" alt="JVM跨平台"></p>
</blockquote>
<ul>
<li>解释器真正意义上所承担的角色就是一个运行时<code>翻译者</code>，将字节码文件中的内容<code>翻译</code>为对应平台的本地机器指令执行</li>
<li>当一条字节码指令被解释执行完成后，接着再根据 PC 寄存器中记录的下一条需要被执行的字节码指令执行解释操作</li>
</ul>
</li>
<li><p>解释器分类</p>
<ul>
<li><p>在 Java 的发展历史里，一共有两套解释执行器</p>
<ul>
<li>古老的字节码解释器：在执行时通过<strong>纯软件代码模拟字节码</strong>的执行，效率非常低下</li>
<li>现在普遍使用的模板解释器：将<strong>每一条字节码和一个模板函数相关联</strong>，模板函数中直接产生这条字节码执行时的机器码，从而很大程度上提高了解释器的性能</li>
</ul>
</li>
<li><p>在 HotSpot VM 中，解释器主要由 <code>Interpreter 模块</code> 和 <code>Code 模块</code> 构成</p>
<ul>
<li><code>Interpreter 模块</code>：实现了解释器的核心功能</li>
<li><code>Code 模块</code>：用于管理 HotSpot VM 在运行时生成的本地机器指令</li>
</ul>
</li>
</ul>
</li>
<li><p>解释器现状</p>
<blockquote>
<p>由于解释器在设计和实现上非常简单，因此除了 Java 语言之外，还有许多高级语言同样也是基于解释器执行的，比如 Python、Perl、Ruby 等。但是在今天，<strong>基于解释器执行已经沦落为低效的代名词</strong>，并且时常被一些 C&#x2F;C++程序员所调侃</p>
<p>为了解决这个问题，JVM 平台支持一种叫作即时编译的技术</p>
<p><strong>即时编译的目的是避免函数被解释执行，而是将整个函数体编译成为机器码，每次函数执行时，只执行编译后的机器码即可</strong>，这种方式可以使执行效率大幅度提升</p>
<p>不过无论如何，基于解释器的执行模式仍然为中间语言的发展做出了不可磨灭的贡献</p>
</blockquote>
</li>
</ol>
<h4 id="5-JIT-编译器"><a href="#5-JIT-编译器" class="headerlink" title="5.JIT 编译器"></a>5.JIT 编译器</h4><ol>
<li><p>Java 代码的执行分类</p>
<ul>
<li>第一种是将源代码编译成字节码文件，然后在运行时通过<strong>解释器将字节码文件转为机器码</strong>执行</li>
<li>第二种是编译执行（直接编译成机器码，但是要知道不同机器上编译的机器码是不一样，而字节码是可以跨平台的）。现代虚拟机为了提高执行效率，会使用即时编译技术（JIT，Just In Time）将方法编译成机器码后再执行</li>
</ul>
<blockquote>
<p>HotSpot VM 是目前市面上高性能虚拟机的代表作之一。它<strong>采用解释器与即时编译器并存的架构</strong></p>
<p>在 Java 虚拟机运行时，解释器和即时编译器能够相互协作，各自取长补短，尽力去选择最合适的方式来权衡编译本地代码的时间和直接解释执行代码的时间</p>
<p>在今天，Java 程序的运行性能早已脱胎换骨，已经达到了可以和 C&#x2F;C++ 程序一较高下的地步</p>
</blockquote>
<blockquote>
<p>有些开发人员会感觉到诧异，<strong>既然 HotSpot VM 中已经内置 JIT 编译器了，那么为什么还需要再使用解释器来“拖累”程序的执行性能呢？</strong>比如 JRockit VM 内部就不包含解释器，字节码全部都依靠即时编译器编译后执行</p>
<p>首先明确： </p>
<ul>
<li><p>当程序启动后，解释器可以马上发挥作用，省去编译的时间，立即执行。</p>
</li>
<li><p>编译器要想发挥作用，把代码编译成本地代码，需要一定的执行时间。但编译为本地代码后，执行效率高</p>
</li>
</ul>
<p>所以： 尽管 JRockit VM 中程序的执行性能会非常高效，但程序在启动时必然需要花费更长的时间来进行编译。对于服务端应用来说，启动时间并非是关注重点，但对于那些看中启动时间的应用场景而言，或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点</p>
<ul>
<li><p><strong>当 Java 虚拟器启动时，解释器可以首先发挥作用，而不必等待即时编译器全部编译完成后再执行，这样可以省去许多不必要的编译时间</strong></p>
</li>
<li><p><strong>随着时间的推移，编译器发挥作用，把越来越多的代码编译成本地代码，获得更高的执行效率</strong></p>
</li>
<li><p><strong>同时，解释执行在编译器进行激进优化不成立的时候，作为编译器的<code>逃生门</code></strong></p>
</li>
</ul>
</blockquote>
</li>
<li><p>HotSpot JVM 执行方式</p>
<blockquote>
<p>当虚拟机启动的时候，<strong>解释器可以首先发挥作用</strong>，而不必等待即时编译器全部编译完成再执行，这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移，即时编译器逐渐发挥作用，根据热点探测功能，<strong>将有价值的字节码编译为本地机器指令</strong>，以换取更高的程序执行效率</p>
</blockquote>
<blockquote>
<p>注意解释执行与编译执行在线上环境微妙的辩证关系</p>
<p><strong>机器在热机状态可以承受的负载要大于冷机状态</strong>。如果以热机状态时的流量进行切流，可能使处于冷机状态的服务器因无法承载流量而假死</p>
<p>在生产环境发布过程中，以分批的方式进行发布，根据机器数量划分成多个批次，每个批次的机器数至多占到整个集群的 1&#x2F;8</p>
<p>曾经有这样的故障案例：某程序员在发布平台进行分批发布，在输入发布总批数时，误填写成分为两批发布。如果是热机状态，在正常情况下一半的机器可以勉强承载流量，但由于刚启动的 JVM 均是解释执行，还没有进行热点代码统计和 JIT 动态编译，导致机器启动之后，当前 1&#x2F;2 发布成功的服务器马上全部宕机，此故障说明了 JIT 的存在。——阿里团队</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E8%A7%A3%E9%87%8A%E7%BC%96%E8%AF%91.png" alt="解释编译"></p>
</blockquote>
</li>
<li><p>概念解释</p>
<blockquote>
<p>Java 语言的<code>编译期</code>其实是一段<code>不确定</code>的操作过程：</p>
<ul>
<li><p>因为它可能是指一个<strong>前端编译器</strong>（其实叫<code>编译器的前端</code>更准确一些）把 <code>.java文件</code>转变成 <code>.class文件</code>的过程</p>
</li>
<li><p>也可能是指虚拟机的<strong>后端运行期编译器</strong>（JIT 编译器，Just In Time Compiler）把字节码转变成机器码的过程</p>
</li>
<li><p>还可能是指使用<strong>静态提前编译器</strong>（AOT 编译器，Ahead of Time Compiler）直接把 <code>.java文件</code>编译成本地机器代码的过程</p>
</li>
</ul>
<p><strong><code>.class文件</code> &#x3D;&#x3D; 字节码文件</strong></p>
</blockquote>
<ul>
<li><p>前端编译器：Sun 的 Javac、Eclipse JDT 中的增量式编译器（ECJ）</p>
</li>
<li><p>JIT 编译器：HotSpot VM 的 C1、C2 编译器</p>
</li>
<li><p>AOT 编译器：GNU Compiler for the Java（GCJ）、Excelsior JET</p>
</li>
</ul>
</li>
<li><p>热点代码及探测技术</p>
<blockquote>
<p>当然是否需要启动 JIT 编译器将字节码直接编译为对应平台的本地机器指令，则需要根据代码被调用执行的频率而定。关于那些需要被编译为本地代码的字节码，也被称之为<code>热点代码</code></p>
<p>JIT 编译器在运行时会针对那些频繁被调用的<code>热点代码</code>做出深度优化，将其直接编译为对应平台的本地机器指令，以此提升 Java 程序的执行性能</p>
<p><strong>一个被多次调用的方法，或者是一个方法体内部循环次数较多的循环体都可以被称之为<code>热点代码</code><strong>，因此都可以通过 JIT 编译器编译为本地机器指令。由于这种编译方式发生在方法的执行过程中，因此被称之为</strong>栈上替换</strong>，或简称为<strong>OSR（On Stack Replacement）编译</strong></p>
<p>一个方法究竟要被调用多少次，或者一个循环体究竟需要执行多少次循环才可以达到这个标准？必然需要一个明确的阈值，JIT 编译器才会将这些<code>热点代码</code>编译为本地机器指令执行。这里主要依靠<strong>热点探测功能</strong></p>
<p><strong>目前 HotSpot VM 所采用的热点探测方式是基于计数器的热点探测</strong></p>
<p>采用基于计数器的热点探测，HotSpot VM 将会为每一个方法都建立 2 个不同类型的计数器</p>
<ul>
<li>方法调用计数器（Invocation Counter）：用于统计方法的调用次数</li>
<li>回边计数器（Back Edge Counter）：用于统计循环体执行的循环次数</li>
</ul>
</blockquote>
<ul>
<li><p>方法调用计数器</p>
<blockquote>
<p>这个计数器就用于统计方法被调用的次数，它的默认阀值在 Client 模式下是 1500 次，在 Server 模式（默认）下是 10000 次。超过这个阈值，就会触发 JIT 编译</p>
<p>这个阀值可以通过虚拟机参数 <code>-XX:CompileThreshold</code>来人为设定</p>
<p>当一个方法被调用时，会先检查该方法是否存在被 JIT 编译过的版本，如果存在，则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本，则将此方法的调用计数器值加 1，然后判断<strong>方法调用计数器与回边计数器值之和</strong>是否超过方法调用计数器的阀值。如果已超过阈值，那么将会向即时编译器提交一个该方法的代码编译请求</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E8%AE%A1%E6%95%B0%E5%99%A8.png" alt="方法调用计数器"></p>
</blockquote>
</li>
<li><p>热点衰减</p>
<blockquote>
<p>如果不做任何设置，方法调用计数器统计的并不是方法被调用的绝对次数，而是一个相对的执行频率，即<strong>一段时间之内方法被调用的次数</strong></p>
<p>当超过一定的时间限度，如果方法的调用次数仍然不足以让它提交给即时编译器编译，那这个方法的调用计数器就会被减少一半，<strong>这个过程称为方法调用计数器热度的衰减（Counter Decay），而这段时间就称为此方法统计的半衰周期（Counter Half Life Time）</strong></p>
<p>进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的，可以使用虚拟机参数 <code>-XX:-UseCounterDecay</code> 来关闭热度衰减，让方法计数器统计方法调用的绝对次数，这样只要系统运行时间足够长，绝大部分方法都会被编译成本地代码</p>
<p>另外，可以使用<code>-XX:CounterHalfLifeTime</code>参数设置半衰周期的时间，单位是秒</p>
</blockquote>
</li>
<li><p>回边计数器</p>
<blockquote>
<p>它的作用是<strong>统计一个方法中循环体代码执行的次数</strong>，在字节码中遇到控制流向后跳转的指令称为<code>回边（Back Edge）</code></p>
<p>建立回边计数器的主要目的是为了触发 <strong>OSR（On StackReplacement）编译</strong>，即<strong>栈上编译</strong>。在一些循环周期比较长的代码段中，当循环达到回边计数器阈值时，JVM 会认为这段是热点代码，JIT 编译器就会将这段代码编译成机器语言并缓存，在该循环时间段内，会直接将执行代码替换，执行缓存的机器语言</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/%E5%9B%9E%E8%BE%B9%E8%AE%A1%E6%95%B0%E5%99%A8.png" alt="回边计数器"></p>
</blockquote>
</li>
</ul>
</li>
<li><p>HotSpotVM 中 JIT 分类</p>
<blockquote>
<p>JIT 的编译器还分为了两种，分别是 C1 和 C2，在 HotSpot VM 中内嵌有两个 JIT 编译器，分别为 Client Compiler 和 Server Compiler，但大多数情况下我们简称为 C1 编译器 和 C2 编译器。开发人员可以通过如下命令显式指定 Java 虚拟机在运行时到底使用哪一种即时编译器，如下所示：</p>
<ul>
<li><code>-client</code>：指定 Java 虚拟机运行在 Client 模式下，并使用 C1 编译器；<strong>C1 编译器会对字节码进行简单和可靠的优化，耗时短</strong>，以达到更快的编译速度</li>
<li><code>-server</code>：指定 Java 虚拟机运行在 server 模式下，并使用 C2 编译器。<strong>C2进行耗时较长的优化，以及激进优化</strong>，但优化的代码执行效率更高</li>
</ul>
<p><strong>分层编译（Tiered Compilation）策略</strong>：程序解释执行（不开启性能监控）可以触发 C1 编译，将字节码编译成机器码，可以进行简单优化，也可以加上性能监控，C2 编译会根据性能监控信息进行激进优化</p>
<p>不过在 Java7 版本之后，一旦开发人员在程序中显式指定命令 <code>-server</code>时，默认将会开启分层编译策略，由 C1 编译器和 C2 编译器相互协作共同来执行编译任务</p>
</blockquote>
<ul>
<li><strong>C1 编译器的优化策略</strong><ul>
<li><strong>方法内联</strong>：将引用的函数代码编译到引用点处，这样可以减少栈帧的生成，减少参数传递以及跳转过程</li>
<li><strong>去虚拟化</strong>：对唯一的实现类进行内联</li>
<li><strong>冗余消除</strong>：在运行期间把一些不会执行的代码折叠掉</li>
</ul>
</li>
<li><strong>C2 编译器的优化策略</strong><ul>
<li><strong>标量替换</strong>：用标量值代替聚合对象的属性值</li>
<li><strong>栈上分配</strong>：对于未逃逸的对象分配对象在栈而不是堆</li>
<li><strong>同步消除</strong>：清除同步操作，通常指 synchronized</li>
</ul>
</li>
</ul>
</li>
</ol>
<h4 id="6-总结"><a href="#6-总结" class="headerlink" title="6.总结"></a>6.总结</h4><blockquote>
<p>一般来讲，JIT 编译出来的机器码性能比解释器高。C2 编译器启动时长比 C1 慢，系统稳定执行以后，C2 编译器执行速度远快于 C1 编译器</p>
</blockquote>
<ol>
<li>写在最后1<ul>
<li>自 JDK10 起，HotSpot 又加入了一个全新的及时编译器：Graal 编译器</li>
<li>编译效果短短几年时间就追评了 C2 编译器，未来可期</li>
<li>目前，带着实验状态标签，需要使用开关参数<code>-XX:+UnlockExperimentalvMOptions -XX:+UseJVMCICompiler</code>去激活才能使用</li>
</ul>
</li>
<li>写在最后2<ul>
<li>jdk9 引入了 AOT 编译器（静态提前编译器，Ahead of Time Compiler）</li>
<li>Java 9 引入了实验性 AOT 编译工具 jaotc。它借助了 Graal 编译器，将所输入的 Java 类文件转换为机器码，并存放至生成的动态共享库之中</li>
<li>所谓 AOT 编译，是与即时编译相对立的一个概念。我们知道，<strong>即时编译指的是在程序的运行过程中，将字节码转换为可在硬件上直接运行的机器码</strong>，并部署至托管环境中的过程。而<strong>AOT 编译指的则是，在程序运行之前，便将字节码转换为机器码的过程</strong><ul>
<li>优点<ul>
<li>Java 虚拟机加载已经预编译成二进制库，可以直接执行。不必等待及时编译器的预热，减少 Java 应用给人带来<code>第一次运行慢</code>的不良体验</li>
</ul>
</li>
<li>缺点：<ul>
<li><strong>破坏了 java “ 一次编译，到处运行”的理念</strong>，必须为每个不同的硬件，OS 编译对应的发行包</li>
<li><strong>降低了 Java 链接过程的动态性</strong>，加载的代码在编译器就必须全部已知</li>
<li>还需要继续优化中，最初只支持 Linux X64 java base</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="13-StringTable"><a href="#13-StringTable" class="headerlink" title="13.StringTable"></a>13.StringTable</h3><h4 id="1-String-的基本特性"><a href="#1-String-的基本特性" class="headerlink" title="1.String 的基本特性"></a>1.String 的基本特性</h4><ol>
<li><p>基本特性</p>
<ul>
<li><p>String：字符串，使用一对””引起来表示</p>
</li>
<li><p>String 声明为 final 的，不可被继承</p>
</li>
<li><p>String 实现了 Serializable 接口：表示字符串是支持序列化的</p>
</li>
<li><p>String 实现了 Comparable 接口：表示 String 可以比较大小</p>
</li>
<li><p><strong>String 在 jdk8 及以前内部定义了 final char[] value 用于存储字符串数据。JDK9 时改为 byte[]</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// String 再也不用 char[] 来存储了，改成了 byte [] 加上编码标记，节约了一些空间</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">String</span> <span class="keyword">implements</span> <span class="title class_">java</span>.io.Serializable, Comparable&lt;String&gt;, CharSequence &#123;</span><br><span class="line">    <span class="meta">@Stable</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">byte</span>[] value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>String 的不可变性</strong></p>
<ul>
<li>当对字符串重新赋值时，需要重写指定内存区域赋值，不能使用原有的 value 进行赋值</li>
<li>当对现有的字符串进行连接操作时，也需要重新指定内存区域赋值，不能使用原有的 value 进行赋值</li>
<li>当调用 String 的 <code>replace()</code> 方法修改指定字符或字符串时，也需要重新指定内存区域赋值，不能使用原有的 value 进行赋值</li>
</ul>
<blockquote>
<p>通过字面量的方式（区别于 new）给一个字符串赋值，此时的字符串值声明在字符串常量池中</p>
<p><strong>字符串常量池是不会存储相同内容的字符串的</strong></p>
<p><strong>String 的 String Pool 是一个固定大小的 Hashtable，默认值大小长度是 1009。如果放进 String Pool 的 String 非常多，就会造成 Hash 冲突严重，从而导致链表会很长，而链表长了后直接会造成的影响就是当调用 String.intern 时性能会大幅下降</strong></p>
<p>使用<code>-XX:StringTablesize</code>可设置 StringTable 的长度</p>
<ul>
<li><p>在 JDK6 中 StringTable 是固定的，就是 1009 的长度，所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize 设置没有要求</p>
</li>
<li><p>在 JDK7 中，StringTable 的长度默认值是 60013，StringTablesize 设置没有要求</p>
</li>
<li><p>在 JDK8 中，设置 StringTable 长度的话，1009 是可以设置的最小值</p>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p>String 在 JDK9 中存储结构变更</p>
<ul>
<li><p><a target="_blank" rel="noopener" href="http://openjdk.java.net/jeps/254">官方网址</a></p>
</li>
<li><p>变更动机</p>
<blockquote>
<p>目前 String 类的实现将字符存储在一个 char 数组中，每个字符使用两个字节（16 位）。从许多不同的应用中收集到的数据表明，<strong>字符串是堆使用的主要组成部分</strong>，此外，大多数字符串对象只包含 Latin-1 字符。这些字符只需要一个字节的存储空间，因此这些字符串对象的内部字符数组中有一半的空间没有被使用</p>
</blockquote>
</li>
<li><p>变更说明</p>
<blockquote>
<p>我们建议将 String 类的内部表示方法从 UTF-16 字符数组改为字节数组加编码标志域。新的 String 类将根据字符串的内容，以 ISO-8859-1&#x2F;Latin-1（每个字符一个字节）或 UTF-16（每个字符两个字节）的方式存储字符编码。编码标志将表明使用的是哪种编码</p>
<p>与字符串相关的类，如 <code>AbstractStringBuilder、StringBuilder 和 StringBuffer</code> 将被更新以使用相同的表示方法，<strong>HotSpot VM 的内在字符串操作也是如此</strong></p>
<p>这纯粹是一个实现上的变化，对现有的公共接口没有变化。目前没有计划增加任何新的公共 API 或其他接口</p>
<p>迄今为止所做的原型设计工作证实了内存占用的预期减少，GC 活动的大幅减少，以及在某些角落情况下的轻微性能倒退</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h4 id="2-String-的内存分配"><a href="#2-String-的内存分配" class="headerlink" title="2.String 的内存分配"></a>2.String 的内存分配</h4><ol>
<li><p>在 Java 语言中有 8 种基本数据类型和一种比较特殊的类型 String。这些类型为了使它们在运行过程中速度更快、更节省内存，都提供了一种常量池的概念</p>
</li>
<li><p>常量池就类似一个 Java 系统级别提供的缓存。8 种基本数据类型的常量池都是系统协调的，<strong>String 类型的常量池比较特殊。它的主要使用方法有两种</strong></p>
<ul>
<li>直接使用双引号声明出来的 String 对象会直接存储在常量池中</li>
<li>如果不是用双引号声明的 String 对象，可以使用 String 提供的 intern() 方法（后面重点介绍）</li>
</ul>
</li>
<li><p>字符串常量池存储位置变更</p>
<blockquote>
<p>简介：在 JDK 7 中，内部字符串不再分配在 Java 堆的永久代中，而是分配在 Java 堆的主要部分（称为年轻代和老年代），与应用程序创建的其他对象一起</p>
<p>这种变化将导致更多的数据驻留在主 Java 堆中，而更少的数据在永久代中，因此可能需要调整堆的大小。大多数应用程序将看到由于这一变化而导致的堆使用的相对较小的差异，但<strong>加载许多类或大量使用 String.intern() 方法的大型应用程序将看到更明显的差异</strong></p>
</blockquote>
<ul>
<li><p>Java 6 及以前，字符串常量池存放在永久代</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/String%E5%B8%B8%E9%87%8F%E6%B1%A0JDK6.png" alt="String常量池JDK6"></p>
</li>
<li><p>Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变，即将<strong>字符串常量池的位置调整到 Java 堆内</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/String%E5%B8%B8%E9%87%8F%E6%B1%A0JDK7.png" alt="String常量池JDK7"></p>
</li>
<li><p>Java8 元空间，字符串常量在堆</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/String%E5%B8%B8%E9%87%8F%E6%B1%A0JDK8.png" alt="String常量池JDK8"></p>
</li>
</ul>
</li>
</ol>
<h4 id="3-String-的基本操作"><a href="#3-String-的基本操作" class="headerlink" title="3.String 的基本操作"></a>3.String 的基本操作</h4><ol>
<li><p>举例1</p>
<blockquote>
<p>Java 语言规范里要求完全相同的字符串字面量，应该包含同样的 Unicode 字符序列（包含同一份码点序列的常量），并且必须是指向同一个 String 类实例</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test1</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.print1n(<span class="string">&quot;1&quot;</span>); <span class="comment">//2321个字符串常量</span></span><br><span class="line">    System.out.println(<span class="string">&quot;2&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;3&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;4&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;5&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;6&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;7&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;8&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;9&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;10&quot;</span>); <span class="comment">//2330个字符串常量</span></span><br><span class="line">    System.out.println(<span class="string">&quot;1&quot;</span>); <span class="comment">//2321个字符串常量</span></span><br><span class="line">    System.out.println(<span class="string">&quot;2&quot;</span>); <span class="comment">//2322个字符串常量</span></span><br><span class="line">    System.out.println(<span class="string">&quot;3&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;4&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;5&quot;</span>);</span><br><span class="line">    System.out.print1n(<span class="string">&quot;6&quot;</span>);</span><br><span class="line">    System.out.print1n(<span class="string">&quot;7&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;8&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;9&quot;</span>);</span><br><span class="line">    System.out.println(<span class="string">&quot;10&quot;</span>);<span class="comment">//2330个字符串常量</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>举例2</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Memory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;<span class="comment">//line 1</span></span><br><span class="line">        <span class="type">int</span> i= <span class="number">1</span>;<span class="comment">//line 2</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();<span class="comment">//line 3</span></span><br><span class="line">        <span class="type">Memory</span> <span class="variable">mem</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Memory</span>();<span class="comment">//Line 4</span></span><br><span class="line">        mem.foo(obj);<span class="comment">//Line 5</span></span><br><span class="line">    &#125;<span class="comment">//Line 9</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">foo</span><span class="params">(Object param)</span> &#123;<span class="comment">//line 6</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> param.toString();<span class="comment">//line 7</span></span><br><span class="line">        System.out.println(str);</span><br><span class="line">    &#125;<span class="comment">//Line 8</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/String%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C%E4%B8%BE%E4%BE%8B2.png" alt="String基本操作举例2"></p>
</li>
</ol>
<h4 id="4-字符串拼接操作"><a href="#4-字符串拼接操作" class="headerlink" title="4.字符串拼接操作"></a>4.字符串拼接操作</h4><blockquote>
<p><strong>常量与常量的拼接结果在常量池，原理是编译期优化</strong></p>
<p><strong>常量池中不会存在相同内容的变量</strong></p>
<p>只要其中有一个是变量，结果就在堆中。<strong>变量拼接的原理是 StringBuilder</strong></p>
<p>如果拼接的结果调用 <code>intern()</code> 方法，则主动将常量池中还没有的字符串对象放入池中，并返回此对象地址</p>
</blockquote>
<ol>
<li><p>举例1</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test1</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 都是常量，前端编译期会进行代码优化</span></span><br><span class="line">    <span class="comment">// 通过idea直接看对应的反编译的class文件，会显示 String s1 = &quot;abc&quot;; 说明做了代码优化</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span> + <span class="string">&quot;b&quot;</span> + <span class="string">&quot;c&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;abc&quot;</span>;</span><br><span class="line">   </span><br><span class="line">    <span class="comment">// true，有上述可知，s1和s2实际上指向字符串常量池中的同一个值</span></span><br><span class="line">    System.out.println(s1 == s2);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>举例2</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">test5</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;javaEE&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;hadoop&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> <span class="string">&quot;javaEEhadoop&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s4</span> <span class="operator">=</span> <span class="string">&quot;javaEE&quot;</span> + <span class="string">&quot;hadoop&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s5</span> <span class="operator">=</span> s1 + <span class="string">&quot;hadoop&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s6</span> <span class="operator">=</span> <span class="string">&quot;javaEE&quot;</span> + s2;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s7</span> <span class="operator">=</span> s1 + s2;</span><br><span class="line"></span><br><span class="line">    System.out.println(s3 == s4); <span class="comment">// true 编译期优化</span></span><br><span class="line">    System.out.println(s3 == s5); <span class="comment">// false s1是变量，不能编译期优化</span></span><br><span class="line">    System.out.println(s3 == s6); <span class="comment">// false s2是变量，不能编译期优化</span></span><br><span class="line">    System.out.println(s3 == s7); <span class="comment">// false s1、s2都是变量</span></span><br><span class="line">    System.out.println(s5 == s6); <span class="comment">// false s5、s6 不同的对象实例</span></span><br><span class="line">    System.out.println(s5 == s7); <span class="comment">// false s5、s7 不同的对象实例</span></span><br><span class="line">    System.out.println(s6 == s7); <span class="comment">// false s6、s7 不同的对象实例</span></span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">s8</span> <span class="operator">=</span> s6.intern();</span><br><span class="line">    System.out.println(s3 == s8); <span class="comment">// true intern之后，s8和s3一样，指向字符串常量池中的&quot;javaEEhadoop&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>举例3</p>
<blockquote>
<p>不使用 final 修饰，即为变量。如 s3 行的 s1 和 s2，会通过 new StringBuilder 进行拼接</p>
<p>使用 final 修饰，即为常量。会在编译器进行代码优化。<strong>在实际开发中，能够使用 final 的，尽量使用</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test6</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s0</span> <span class="operator">=</span> <span class="string">&quot;beijing&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;bei&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;jing&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> s1 + s2;</span><br><span class="line">    System.out.println(s0 == s3); <span class="comment">// false s3指向对象实例，s0指向字符串常量池中的&quot;beijing&quot;</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">s7</span> <span class="operator">=</span> <span class="string">&quot;shanxi&quot;</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">String</span> <span class="variable">s4</span> <span class="operator">=</span> <span class="string">&quot;shan&quot;</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">String</span> <span class="variable">s5</span> <span class="operator">=</span> <span class="string">&quot;xi&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s6</span> <span class="operator">=</span> s4 + s5;</span><br><span class="line">    System.out.println(s6 == s7); <span class="comment">// true s4和s5是final修饰的，编译期就能确定s6的值了</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>举例4</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test3</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s1</span> <span class="operator">=</span> <span class="string">&quot;a&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;b&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> <span class="string">&quot;ab&quot;</span>;</span><br><span class="line">    <span class="type">String</span> <span class="variable">s4</span> <span class="operator">=</span> s1 + s2;</span><br><span class="line">    System.out.println(s3==s4); <span class="comment">// false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>字节码如下</p>
<p>可以发现 <code>s1 + s2</code> 实际上是 new 了一个 StringBuilder 对象，并使用了 append 方法将 s1 和 s2 添加进来，最后调用了 toString 方法赋给 s4</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"> <span class="number">0</span> ldc #<span class="number">2</span> &lt;a&gt;</span><br><span class="line"> <span class="number">2</span> astore_1</span><br><span class="line"> <span class="number">3</span> ldc #<span class="number">3</span> &lt;b&gt;</span><br><span class="line"> <span class="number">5</span> astore_2</span><br><span class="line"> <span class="number">6</span> ldc #<span class="number">4</span> &lt;ab&gt;</span><br><span class="line"> <span class="number">8</span> astore_3</span><br><span class="line"> <span class="number">9</span> <span class="keyword">new</span> #<span class="number">5</span> &lt;java/lang/StringBuilder&gt;</span><br><span class="line"><span class="number">12</span> dup</span><br><span class="line"><span class="number">13</span> invokespecial #<span class="number">6</span> &lt;java/lang/StringBuilder.&lt;init&gt;&gt;</span><br><span class="line"><span class="number">16</span> aload_1</span><br><span class="line"><span class="number">17</span> invokevirtual #<span class="number">7</span> &lt;java/lang/StringBuilder.append&gt;</span><br><span class="line"><span class="number">20</span> aload_2</span><br><span class="line"><span class="number">21</span> invokevirtual #<span class="number">7</span> &lt;java/lang/StringBuilder.append&gt;</span><br><span class="line"><span class="number">24</span> invokevirtual #<span class="number">8</span> &lt;java/lang/StringBuilder.toString&gt;</span><br><span class="line"><span class="number">27</span> astore <span class="number">4</span></span><br><span class="line"><span class="number">29</span> getstatic #<span class="number">9</span> &lt;java/lang/System.out&gt;</span><br><span class="line"><span class="number">32</span> aload_3</span><br><span class="line"><span class="number">33</span> aload <span class="number">4</span></span><br><span class="line"><span class="number">35</span> if_acmpne <span class="number">42</span> (+<span class="number">7</span>)</span><br><span class="line"><span class="number">38</span> iconst_1</span><br><span class="line"><span class="number">39</span> goto <span class="number">43</span> (+<span class="number">4</span>)</span><br><span class="line"><span class="number">42</span> iconst_0</span><br><span class="line"><span class="number">43</span> invokevirtual #<span class="number">10</span> &lt;java/io/PrintStream.println&gt;</span><br><span class="line"><span class="number">46</span> <span class="keyword">return</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>String、StringBuilder、StringBuffer 效率对比</p>
<blockquote>
<p>本实验进行 5 万次循环，String 拼接方式的时间是 StringBuilder.append 方式的约 8000 倍，StringBuffer.append() 方式的时间是 StringBuilder.append()方式的约 4 倍</p>
<p>可以看到，通过 StringBuilder 的 append 方式的速度，要比直接对 String 使用 + 拼接的方式快得多</p>
<p>那么，在实际开发中，对于需要多次或大量拼接的操作，在不考虑线程安全问题时，应<strong>尽可能使用 StringBuilder 进行 append 操作</strong></p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">times</span> <span class="operator">=</span> <span class="number">50000</span>;</span><br><span class="line">        <span class="comment">// String</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        testString(times);</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">&quot;String: &quot;</span> + (end - start) + <span class="string">&quot;ms&quot;</span>);          <span class="comment">// String: 7963ms</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// StringBuilder</span></span><br><span class="line">        start = System.currentTimeMillis();</span><br><span class="line">        testStringBuilder(times);</span><br><span class="line">        end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">&quot;StringBuilder: &quot;</span> + (end - start) + <span class="string">&quot;ms&quot;</span>);   <span class="comment">// StringBuilder: 1ms</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// StringBuffer</span></span><br><span class="line">        start = System.currentTimeMillis();</span><br><span class="line">        testStringBuffer(times);</span><br><span class="line">        end = System.currentTimeMillis();</span><br><span class="line">        System.out.println(<span class="string">&quot;StringBuffer: &quot;</span> + (end - start) + <span class="string">&quot;ms&quot;</span>);    <span class="comment">// StringBuffer: 4ms</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testString</span><span class="params">(<span class="type">int</span> times)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; times; i++) &#123;</span><br><span class="line">            str += <span class="string">&quot;test&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testStringBuilder</span><span class="params">(<span class="type">int</span> times)</span> &#123;</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; times; i++) &#123;</span><br><span class="line">            sb.append(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testStringBuffer</span><span class="params">(<span class="type">int</span> times)</span> &#123;</span><br><span class="line">        <span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; times; i++) &#123;</span><br><span class="line">            sb.append(<span class="string">&quot;test&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>除此之外，还有那些操作能够帮助我们提高字符串方面的运行效率呢？</p>
<p>StringBuilder 空参构造器的初始化大小为 16。那么，如果提前知道需要拼接 String 的个数，就应该直接使用<strong>带参构造器指定 capacity</strong>，以减少扩容的次数（扩容的逻辑可以自行查看源代码）</p>
</blockquote>
</li>
</ol>
<h4 id="5-intern-的使用"><a href="#5-intern-的使用" class="headerlink" title="5.intern() 的使用"></a>5.<code>intern()</code> 的使用</h4><ol>
<li><p><code>intern()</code> 方法基本介绍</p>
<ul>
<li><p>当调用 intern 方法时，如果池子里已经包含了一个与这个 String 对象相等的字符串，正如 equals(Object) 方法所确定的，那么池子里的字符串会被返回。否则，这个 String 对象被添加到池中，并返回这个 String 对象的引用</p>
</li>
<li><p>由此可见，对于任何两个字符串 s 和 t，当且仅当 <code>s.equals(t)</code> 为真时，<code>s.intern() == t.intern()</code> 为真</p>
</li>
<li><p>所有字面字符串和以字符串为值的常量表达式都是 interned</p>
</li>
<li><p><strong>返回一个与此字符串内容相同的字符串，但保证是来自一个唯一的字符串池</strong></p>
</li>
<li><p>intern 是一个 native 方法，调用的是底层 C 的方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">native</span> String <span class="title function_">intern</span><span class="params">()</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>如果不是用双引号声明的 String 对象，可以使用 String 提供的 intern 方法，它会从字符串常量池中查询当前字符串是否存在，若不存在就会将当前字符串放入常量池中</p>
</li>
<li><p>通俗点讲，Interned string 就是确保字符串在内存里只有一份拷贝，这样可以节约内存空间，加快字符串操作任务的执行速度。注意，这个值会被存放在字符串内部池（String Intern Pool）</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/intern.png" alt="intern"></p>
</li>
</ul>
</li>
<li><p><code>intern()</code> 的使用：JDK6 VS JDK7&#x2F;8</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ① String s = new String(&quot;1&quot;) * 创建了两个对象 *</span></span><br><span class="line"><span class="comment"> * 堆空间中一个 new 对象 </span></span><br><span class="line"><span class="comment"> * 字符串常量池中一个字符串常量&quot;1&quot;（注意：此时字符串常量池中已有&quot;1&quot;） </span></span><br><span class="line"><span class="comment"> * ② s.intern()由于字符串常量池中已存在&quot;1&quot;</span></span><br><span class="line"><span class="comment"> * s 指向的是堆空间中的对象地址</span></span><br><span class="line"><span class="comment"> * s2 指向的是堆空间中常量池中&quot;1&quot;的地址</span></span><br><span class="line"><span class="comment"> * 所以不相等</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;1&quot;</span>);</span><br><span class="line">s.intern();</span><br><span class="line"><span class="type">String</span> <span class="variable">s2</span> <span class="operator">=</span> <span class="string">&quot;1&quot;</span>;</span><br><span class="line">System.out.println(s==s2); <span class="comment">// jdk1.6 false jdk7/8 false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * ① String s3 = new String(&quot;1&quot;) + new String(&quot;1&quot;)</span></span><br><span class="line"><span class="comment"> * 等价于new String（&quot;11&quot;），但是，常量池中并不生成字符串&quot;11&quot;</span></span><br><span class="line"><span class="comment"> * ② s3.intern() * 由于此时常量池中并无&quot;11&quot;，所以把s3中记录的对象的地址存入常量池</span></span><br><span class="line"><span class="comment"> * 所以s3 和 s4 指向的都是一个地址</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">String</span> <span class="variable">s3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;1&quot;</span>) + <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;1&quot;</span>);</span><br><span class="line">s3.intern();</span><br><span class="line"><span class="type">String</span> <span class="variable">s4</span> <span class="operator">=</span> <span class="string">&quot;11&quot;</span>;</span><br><span class="line">System.out.println(s3==s4); <span class="comment">//jdk1.6 false jdk7/8 true</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>总结 String 的 intern()的使用：</p>
<p>JDK1.6 中，将这个字符串对象尝试放入串池</p>
<ul>
<li>如果串池中有，则并不会放入。返回已有的串池中的对象的地址</li>
<li><strong>如果没有，会把此对象复制一份，放入串池，并返回串池中的对象地址</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/internJDK6.png" alt="internJDK6"></p>
<p>JDK1.7 起，将这个字符串对象尝试放入串池</p>
<ul>
<li>如果串池中有，则并不会放入。返回已有的串池中的对象的地址</li>
<li><strong>如果没有，则会把对象的引用地址复制一份，放入串池，并返回串池中的引用地址</strong></li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/internJDK7.png" alt="internJDK7"></p>
</blockquote>
</li>
<li><p><code>intern()</code> 练习</p>
<ul>
<li><p>练习前瞻</p>
<blockquote>
<p><code>new String(&quot;ab&quot;)</code> 会创建几个对象？</p>
<ul>
<li>对象1：new 关键字在对空间创建的对象</li>
<li>对象2：字符串常量池中的对象 “ab”</li>
</ul>
<p><code>new String(&quot;a&quot;) + new String(&quot;b&quot;)</code> 会创建几个对象？</p>
<ul>
<li>对象1：new StringBuilder()</li>
<li>对象2：new String(“a”)</li>
<li>对象3：字符串常量池中的 “a”</li>
<li>对象4：new String(“b”)</li>
<li>对象5：字符串常量中的 “b”</li>
<li>对象6：StringBuilder 的 toString 创建的对象，相当于 new String(“ab”)；但是字符串常量池中没有生成 “ab”</li>
</ul>
</blockquote>
</li>
<li><p>练习1</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/intern%E7%BB%83%E4%B9%A01.1.png" alt="intern练习1.1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/intern%E7%BB%83%E4%B9%A01.2.png" alt="intern练习1.2"></p>
</li>
<li><p>练习2</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/intern%E7%BB%83%E4%B9%A02.png" alt="intern练习2"></p>
</li>
</ul>
</li>
<li><p><code>intern()</code> 的效率测试：空间角度</p>
<blockquote>
<p><strong>结论</strong>：对于程序中大量使用存在的字符串时，尤其存在很多已经重复的字符串时，使用 <code>intern()</code> 方法能够节省内存空间</p>
<p>大的网站平台，需要内存中存储大量的字符串。比如社交网站，很多人都存储：北京市、海淀区等信息。这时候如果字符串都调用 <code>intern()</code> 方法，就会很明显降低内存的大小</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_COUNT</span> <span class="operator">=</span> <span class="number">1000</span> * <span class="number">10000</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> String[] arr = <span class="keyword">new</span> <span class="title class_">String</span>[MAX_COUNT];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        Integer[] data = <span class="keyword">new</span> <span class="title class_">Integer</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>&#125;;</span><br><span class="line">        <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; MAX_COUNT; i++) &#123;</span><br><span class="line">             arr[i] = <span class="keyword">new</span> <span class="title class_">String</span>(String.valueOf(data[i%data.length]));</span><br><span class="line"><span class="comment">//            arr[i] = new String(String.valueOf(data[i % data.length])).intern();</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="comment">// 不使用intern：4256ms</span></span><br><span class="line">        <span class="comment">// 使用intern：1395ms</span></span><br><span class="line">        <span class="comment">// 时间不重要，重点在空间效率的提升</span></span><br><span class="line">        System.out.println(<span class="string">&quot;花费的时间为：&quot;</span> + (end - start));</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.getStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="6-StringTable-的垃圾回收"><a href="#6-StringTable-的垃圾回收" class="headerlink" title="6.StringTable 的垃圾回收"></a>6.StringTable 的垃圾回收</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringGCTest</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * -Xms15m -Xmx15m -XX:+PrintGCDetails</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">            String.valueOf(i).intern();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>运行结果</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">[GC (Allocation Failure) [PSYoungGen: 4096K-&gt;504K(4608K)] 4096K-&gt;1689K(15872K), <span class="number">0.0581583</span> secs] </span><br><span class="line">[Times: user=<span class="number">0.00</span> sys=<span class="number">0.00</span>, real=<span class="number">0.06</span> secs] </span><br><span class="line">[GC (Allocation Failure) [PSYoungGen: 4600K-&gt;504K(4608K)] 5785K-&gt;2310K(15872K), <span class="number">0.0015621</span> secs] </span><br><span class="line">[Times: user=<span class="number">0.00</span> sys=<span class="number">0.00</span>, real=<span class="number">0.00</span> secs] </span><br><span class="line">[GC (Allocation Failure) [PSYoungGen: 4600K-&gt;504K(4608K)] 6406K-&gt;2350K(15872K), <span class="number">0.0034849</span> secs] </span><br><span class="line">[Times: user=<span class="number">0.00</span> sys=<span class="number">0.00</span>, real=<span class="number">0.00</span> secs] </span><br><span class="line">Heap PSYoungGen      total 4608K, used 1919K [<span class="number">0x00000000ffb00000</span>, <span class="number">0x0000000100000000</span>, <span class="number">0x0000000100000000</span>)  </span><br><span class="line">eden space 4096K, <span class="number">34</span>% used [<span class="number">0x00000000ffb00000</span>,<span class="number">0x00000000ffc61d30</span>,<span class="number">0x00000000fff00000</span>)  </span><br><span class="line">from space 512K, <span class="number">98</span>% used [<span class="number">0x00000000fff00000</span>,<span class="number">0x00000000fff7e010</span>,<span class="number">0x00000000fff80000</span>)  </span><br><span class="line">to   space 512K, <span class="number">0</span>% used [<span class="number">0x00000000fff80000</span>,<span class="number">0x00000000fff80000</span>,<span class="number">0x0000000100000000</span>)</span><br><span class="line">ParOldGen       total 11264K, used 1846K [<span class="number">0x00000000ff000000</span>, <span class="number">0x00000000ffb00000</span>, <span class="number">0x00000000ffb00000</span>)</span><br><span class="line">object space 11264K, <span class="number">16</span>% used [<span class="number">0x00000000ff000000</span>,<span class="number">0x00000000ff1cd9b0</span>,<span class="number">0x00000000ffb00000</span>) </span><br><span class="line">Metaspace       used 3378K, capacity 4496K, committed 4864K, reserved 1056768K <span class="keyword">class</span> <span class="title class_">space</span>    used 361K, capacity 388K, committed 512K, reserved 1048576K</span><br></pre></td></tr></table></figure>

<h4 id="7-G1-中的-String-去重操作"><a href="#7-G1-中的-String-去重操作" class="headerlink" title="7.G1 中的 String 去重操作"></a>7.G1 中的 String 去重操作</h4><ol>
<li><p><a target="_blank" rel="noopener" href="http://openjdk.java.net/jeps/192">官方网址</a></p>
</li>
<li><p>去重操作现状</p>
<blockquote>
<p><strong>注意这里说的重复，指的是在堆中的数据，而不是常量池中的，因为常量池中的本身就不会重复</strong></p>
<p>背景：对许多 Java 应用（有大的也有小的）做的测试得出以下结果：</p>
<ul>
<li>堆存活数据集合里面 string 对象占了 25%</li>
<li>堆存活数据集合里面重复的 string 对象有 13.5%</li>
<li>string 对象的平均长度是 45</li>
</ul>
<p>目前，许多大规模的 Java 应用程序在内存上遇到了瓶颈</p>
<p>测量表明，在这些类型的应用程序中，<strong>大约 25% 的 Java 堆实时数据集被 String 对象所消耗。此外，这些 String 对象中大约有一半是重复的</strong>，其中重复意味着 <code>string1.equals(string2)</code>是真的</p>
<p>在堆上有重复的 String 对象，从本质上讲是一种内存的浪费</p>
<p>这个项目将在 G1 垃圾收集器中实现自动和持续的 String 重复数据删除，以避免浪费内存，减少内存占用</p>
</blockquote>
</li>
<li><p>去重操作实现步骤</p>
<ul>
<li>当垃圾收集器工作的时候，会访问堆上存活的对象。<strong>对每一个访问的对象都会检查是否是候选的要去重的 String 对象</strong></li>
<li>如果是，把这个对象的一个引用插入到队列中等待后续的处理。一个去重的线程在后台运行，处理这个队列。处理队列的一个元素意味着从队列删除这个元素，然后尝试去重它引用的 string 对象</li>
<li>使用一个 hashtable 来记录所有的被 String 对象使用的不重复的 char 数组。当去重的时候，会查这个 hashtable，来看堆上是否已经存在一个一模一样的 char 数组</li>
<li>如果存在，String 对象会被调整引用那个数组，释放对原来的数组的引用，最终会被垃圾收集器回收掉</li>
<li>如果查找失败，char 数组会被插入到 hashtable，这样以后的时候就可以共享这个数组了</li>
</ul>
</li>
<li><p>命令行选项</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"># 开启String去重，默认是不开启的，需要手动开启</span><br><span class="line">UseStringDeduplication(bool)  </span><br><span class="line"># 打印详细的去重统计信息 </span><br><span class="line">PrintStringDeduplicationStatistics(bool)  </span><br><span class="line"># 达到这个年龄的String对象被认为是去重的候选对象</span><br><span class="line">StringpeDuplicationAgeThreshold(uintx)</span><br></pre></td></tr></table></figure></li>
</ol>
<h3 id="14-垃圾回收（详见下篇）"><a href="#14-垃圾回收（详见下篇）" class="headerlink" title="14.垃圾回收（详见下篇）"></a><a target="_blank" rel="noopener" href="https://zcmmmm.gitee.io/2022/10/07/JVM%20%E7%95%AA%E5%A4%96-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%8B%AC%E7%AB%8B%E7%AF%87/">14.垃圾回收（详见下篇）</a></h3></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/09/28/JVM%20%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/">https://gitee.com/zcmmmm/zcmmmm/2022/09/28/JVM%20%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/JVM/">JVM</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/10/07/JVM%20%E7%95%AA%E5%A4%96-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%8B%AC%E7%AB%8B%E7%AF%87/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">JVM 番外-垃圾回收独立篇</div></div></a></div><div class="next-post pull-right"><a href="/2022/09/24/JUC%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JUC/JUC.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">JUC并发编程</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/10/11/JVM%20%E4%B8%8B%E7%AF%87-%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98%E7%AF%87/" title="JVM 下篇-性能监控与调优篇"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-11</div><div class="title">JVM 下篇-性能监控与调优篇</div></div></a></div><div><a href="/2022/10/08/JVM%20%E4%B8%AD%E7%AF%87-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E7%AF%87/" title="JVM 中篇-字节码与类的加载篇"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-08</div><div class="title">JVM 中篇-字节码与类的加载篇</div></div></a></div><div><a href="/2022/10/07/JVM%20%E7%95%AA%E5%A4%96-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%8B%AC%E7%AB%8B%E7%AF%87/" title="JVM 番外-垃圾回收独立篇"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-07</div><div class="title">JVM 番外-垃圾回收独立篇</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#JVM-%E4%B8%8A%E7%AF%87-%E5%86%85%E5%AD%98%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="toc-text">JVM 上篇-内存与垃圾回收</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-JVM-%E4%B8%8E-Java-%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84"><span class="toc-text">1.JVM 与 Java 体系结构</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-Java-%E5%8F%8A-JVM-%E7%AE%80%E4%BB%8B"><span class="toc-text">1.Java 及 JVM 简介</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-Java-%E5%8F%91%E5%B1%95%E7%9A%84%E9%87%8D%E5%A4%A7%E4%BA%8B%E4%BB%B6"><span class="toc-text">2.Java 发展的重大事件</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B8%8E-Java-%E8%99%9A%E6%8B%9F%E6%9C%BA"><span class="toc-text">3.虚拟机与 Java 虚拟机</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-JVM-%E7%9A%84%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84"><span class="toc-text">4.JVM 的整体结构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-Java-%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B"><span class="toc-text">5.Java 代码执行流程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-JVM-%E7%9A%84%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%9E%8B"><span class="toc-text">6.JVM 的架构模型</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-JVM-%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="toc-text">7.JVM 的生命周期</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-JVM-%E7%9A%84%E5%8F%91%E5%B1%95%E5%8E%86%E7%A8%8B"><span class="toc-text">8.JVM 的发展历程</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%AD%90%E7%B3%BB%E7%BB%9F"><span class="toc-text">2.类加载子系统</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84%E6%A6%82%E8%BF%B0"><span class="toc-text">1.内存结构概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E4%B8%8E%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B"><span class="toc-text">2.类加载器与类加载过程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%88%86%E7%B1%BB"><span class="toc-text">3.类加载器分类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-ClassLoader-%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E"><span class="toc-text">4.ClassLoader 的使用说明</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6"><span class="toc-text">5.双亲委派机制</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E5%85%B6%E4%BB%96"><span class="toc-text">6.其他</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E6%A6%82%E8%BF%B0%E5%8F%8A%E7%BA%BF%E7%A8%8B"><span class="toc-text">3.运行时数据区概述及线程</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA"><span class="toc-text">1.运行时数据区</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E7%BA%BF%E7%A8%8B"><span class="toc-text">2.线程</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8%EF%BC%88PC-%E5%AF%84%E5%AD%98%E5%99%A8%EF%BC%89"><span class="toc-text">4.程序计数器（PC 寄存器）</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8"><span class="toc-text">1.程序计数器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-CPU-%E6%97%B6%E9%97%B4%E7%89%87"><span class="toc-text">2.CPU 时间片</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88"><span class="toc-text">5.虚拟机栈</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88%E6%A6%82%E8%BF%B0"><span class="toc-text">1.虚拟机栈概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E6%A0%88%E7%9A%84%E5%AD%98%E5%82%A8%E5%8D%95%E4%BD%8D"><span class="toc-text">2.栈的存储单位</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E8%A1%A8%EF%BC%88Local-Variables-%E6%9C%AC%E5%9C%B0%E5%8F%98%E9%87%8F%E8%A1%A8%EF%BC%89"><span class="toc-text">3.局部变量表（Local Variables 本地变量表）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E6%93%8D%E4%BD%9C%E6%95%B0%E6%A0%88"><span class="toc-text">4.操作数栈</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E4%BB%A3%E7%A0%81%E8%BF%BD%E8%B8%AA"><span class="toc-text">5.代码追踪</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E6%A0%88%E9%A1%B6%E7%BC%93%E5%AD%98%E6%8A%80%E6%9C%AF"><span class="toc-text">6.栈顶缓存技术</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5"><span class="toc-text">7.动态链接</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E6%96%B9%E6%B3%95%E7%9A%84%E8%B0%83%E7%94%A8%EF%BC%9A%E8%A7%A3%E6%9E%90%E5%92%8C%E5%88%86%E6%B4%BE"><span class="toc-text">8.方法的调用：解析和分派</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#9-%E6%96%B9%E6%B3%95%E8%BF%94%E5%9B%9E%E5%9C%B0%E5%9D%80"><span class="toc-text">9.方法返回地址</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#10-%E4%B8%80%E4%BA%9B%E9%99%84%E5%8A%A0%E4%BF%A1%E6%81%AF"><span class="toc-text">10.一些附加信息</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#11-%E6%A0%88%E7%9B%B8%E5%85%B3%E9%9D%A2%E8%AF%95%E9%A2%98"><span class="toc-text">11.栈相关面试题</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%8E%A5%E5%8F%A3"><span class="toc-text">6.本地方法接口</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E4%BB%80%E4%B9%88%E6%98%AF%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95"><span class="toc-text">1.什么是本地方法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8-native-method"><span class="toc-text">2.为什么使用 native method</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%A0%88"><span class="toc-text">7.本地方法栈</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5"><span class="toc-text">1.相关概念</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3"><span class="toc-text">2.深入理解</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-%E5%A0%86"><span class="toc-text">8.堆</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E5%A0%86%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E8%BF%B0"><span class="toc-text">1.堆的核心概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E8%AE%BE%E7%BD%AE%E5%A0%86%E5%86%85%E5%AD%98%E5%A4%A7%E5%B0%8F%E5%92%8C-OOM"><span class="toc-text">2.设置堆内存大小和 OOM</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E5%B9%B4%E8%BD%BB%E4%BB%A3%E4%B8%8E%E8%80%81%E5%B9%B4%E4%BB%A3"><span class="toc-text">3.年轻代与老年代</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E5%9B%BE%E8%A7%A3%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E8%BF%87%E7%A8%8B"><span class="toc-text">4.图解对象分配过程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-Minor-GC%EF%BC%88YGC%EF%BC%89%E3%80%81Major-GC%E3%80%81Full-GC%EF%BC%88FGC%EF%BC%89"><span class="toc-text">5.Minor GC（YGC）、Major GC、Full GC（FGC）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E5%A0%86%E7%A9%BA%E9%97%B4%E5%88%86%E4%BB%A3%E6%80%9D%E6%83%B3"><span class="toc-text">6.堆空间分代思想</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E7%AD%96%E7%95%A5"><span class="toc-text">7.内存分配策略</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E4%B8%BA%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E5%86%85%E5%AD%98%EF%BC%9ATLAB"><span class="toc-text">8.为对象分配内存：TLAB</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#9-%E5%B0%8F%E7%BB%93%EF%BC%9A%E5%A0%86%E7%A9%BA%E9%97%B4%E7%9A%84%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE"><span class="toc-text">9.小结：堆空间的参数设置</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#10-%E5%A0%86%E6%98%AF%E5%88%86%E9%85%8D%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%94%AF%E4%B8%80%E9%80%89%E6%8B%A9%E5%90%97%EF%BC%9F"><span class="toc-text">10.堆是分配对象的唯一选择吗？</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#11-%E6%9C%AC%E7%AB%A0%E5%B0%8F%E7%BB%93"><span class="toc-text">11.本章小结</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#9-%E6%96%B9%E6%B3%95%E5%8C%BA"><span class="toc-text">9.方法区</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E6%A0%88%E3%80%81%E5%A0%86%E3%80%81%E6%96%B9%E6%B3%95%E5%8C%BA%E7%9A%84%E4%BA%A4%E4%BA%92%E5%85%B3%E7%B3%BB"><span class="toc-text">1.栈、堆、方法区的交互关系</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E6%96%B9%E6%B3%95%E5%8C%BA%E7%9A%84%E7%90%86%E8%A7%A3"><span class="toc-text">2.方法区的理解</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E8%AE%BE%E7%BD%AE%E6%96%B9%E6%B3%95%E5%8C%BA%E5%A4%A7%E5%B0%8F%E4%B8%8E-OOM"><span class="toc-text">3.设置方法区大小与 OOM</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E6%96%B9%E6%B3%95%E5%8C%BA%E5%86%85%E9%83%A8%E7%BB%93%E6%9E%84"><span class="toc-text">4.方法区内部结构</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-%E6%96%B9%E6%B3%95%E5%8C%BA%E4%BD%BF%E7%94%A8%E4%B8%BE%E4%BE%8B"><span class="toc-text">5.方法区使用举例</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E6%96%B9%E6%B3%95%E5%8C%BA%E6%BC%94%E8%BF%9B%E7%BB%86%E8%8A%82"><span class="toc-text">6.方法区演进细节</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-%E6%96%B9%E6%B3%95%E5%8C%BA%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="toc-text">7.方法区的垃圾回收</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#8-%E6%80%BB%E7%BB%93%E4%B8%8E%E9%9D%A2%E8%AF%95%E9%A2%98"><span class="toc-text">8.总结与面试题</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#10-%E5%AF%B9%E8%B1%A1%E5%AE%9E%E4%BE%8B%E5%8C%96%E4%B8%8E%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D"><span class="toc-text">10.对象实例化与访问定位</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%AE%9E%E4%BE%8B%E5%8C%96"><span class="toc-text">1.对象的实例化</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80"><span class="toc-text">2.对象的内存布局</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D"><span class="toc-text">3.对象的访问定位</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#11-%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98"><span class="toc-text">11.直接内存</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98%E6%A6%82%E8%BF%B0"><span class="toc-text">1.直接内存概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-%E9%9D%9E%E7%9B%B4%E6%8E%A5%E7%BC%93%E5%86%B2%E5%8C%BA"><span class="toc-text">2.非直接缓冲区</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E7%9B%B4%E6%8E%A5%E7%BC%93%E5%86%B2%E5%8C%BA-%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98"><span class="toc-text">3.直接缓冲区(直接内存)</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#12-%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E"><span class="toc-text">12.执行引擎</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E6%A6%82%E8%BF%B0"><span class="toc-text">1.执行引擎概述</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-Java-%E4%BB%A3%E7%A0%81%E7%BC%96%E8%AF%91%E5%92%8C%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B"><span class="toc-text">2.Java 代码编译和执行过程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-%E6%9C%BA%E5%99%A8%E7%A0%81%E3%80%81%E6%8C%87%E4%BB%A4%E3%80%81%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80"><span class="toc-text">3.机器码、指令、汇编语言</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E8%A7%A3%E9%87%8A%E5%99%A8"><span class="toc-text">4.解释器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-JIT-%E7%BC%96%E8%AF%91%E5%99%A8"><span class="toc-text">5.JIT 编译器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-%E6%80%BB%E7%BB%93"><span class="toc-text">6.总结</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#13-StringTable"><span class="toc-text">13.StringTable</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#1-String-%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%89%B9%E6%80%A7"><span class="toc-text">1.String 的基本特性</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-String-%E7%9A%84%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D"><span class="toc-text">2.String 的内存分配</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-String-%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C"><span class="toc-text">3.String 的基本操作</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#4-%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%BC%E6%8E%A5%E6%93%8D%E4%BD%9C"><span class="toc-text">4.字符串拼接操作</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#5-intern-%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="toc-text">5.intern() 的使用</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#6-StringTable-%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6"><span class="toc-text">6.StringTable 的垃圾回收</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#7-G1-%E4%B8%AD%E7%9A%84-String-%E5%8E%BB%E9%87%8D%E6%93%8D%E4%BD%9C"><span class="toc-text">7.G1 中的 String 去重操作</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#14-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%EF%BC%88%E8%AF%A6%E8%A7%81%E4%B8%8B%E7%AF%87%EF%BC%89"><span class="toc-text">14.垃圾回收（详见下篇）</span></a></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>